package com.eleybourn.bookcatalogue;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import com.eleybourn.bookcatalogue.backup.CsvImporter;
import com.eleybourn.bookcatalogue.backup.Importer;
import com.eleybourn.bookcatalogue.backup.LocalCoverFinder;
import com.eleybourn.bookcatalogue.utils.Logger;
import com.eleybourn.bookcatalogue.utils.StorageUtils;
/**
* Class to handle import in a separate thread.
*
* @author Philip Warner
*/
public class ImportThread extends ManagedTask {
public static String UTF8 = "utf8";
public static int BUFFER_SIZE = 8192;
private final File mFile;
private String mFileSpec;
private boolean mFileIsForeign;
private final String mSharedStoragePath;
private CatalogueDBAdapter mDbHelper;
private LocalCoverFinder mCoverFinder;
public static class ImportException extends RuntimeException {
private static final long serialVersionUID = 1660687786319003483L;
public ImportException(String s) {
super(s);
}
};
//private int mImportUpdated;
//private int mImportCreated;
public ImportThread(TaskManager manager, String fileSpec) throws IOException {
super(manager);
mFile = new File(fileSpec);
// Changed getCanonicalPath to getAbsolutePath based on this bug in Android 2.1:
// http://code.google.com/p/android/issues/detail?id=4961
mFileSpec = mFile.getAbsolutePath();
mSharedStoragePath = StorageUtils.getSharedStorage().getAbsolutePath();
mDbHelper = new CatalogueDBAdapter(BookCatalogueApp.context);
mDbHelper.open();
mFileIsForeign = !(mFileSpec.startsWith(mSharedStoragePath));
mCoverFinder = new LocalCoverFinder(mFile.getParent(), mSharedStoragePath);
//getMessageSwitch().addListener(getSenderId(), taskHandler, false);
//Debug.startMethodTracing();
}
@Override
protected void onThreadFinish() {
cleanup();
}
// /**
// * This program reads a text file line by line and print to the console. It uses
// * FileOutputStream to read the file.
// */
// private ArrayList<String> readFile(String fileSpec) {
// ArrayList<String> importedString = new ArrayList<String>();
//
// try {
// BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileSpec), UTF8),BUFFER_SIZE);
// String line = "";
// while ((line = in.readLine()) != null) {
// importedString.add(line);
// }
// in.close();
// } catch (FileNotFoundException e) {
// doToast(BookCatalogueApp.getResourceString(R.string.import_failed));
// Logger.logError(e);
// } catch (IOException e) {
// doToast(BookCatalogueApp.getResourceString(R.string.import_failed));
// Logger.logError(e);
// }
// return importedString;
// }
private Importer.OnImporterListener mImportListener = new Importer.OnImporterListener() {
@Override
public void onProgress(String message, int position) {
if (position > 0) {
mManager.doProgress(ImportThread.this, message, position);
} else {
mManager.doProgress(message);
}
}
@Override
public boolean isCancelled() {
return ImportThread.this.isCancelled();
}
@Override
public void setMax(int max) {
mManager.setMax(ImportThread.this, max);
}
};
@Override
protected void onRun() {
// Initialize
//ArrayList<String> export = readFile(mFileSpec);
CsvImporter importer = new CsvImporter();
FileInputStream in = null;
try {
in = new FileInputStream(mFileSpec);
importer.importBooks(in, mCoverFinder, mImportListener, Importer.IMPORT_ALL);
if (isCancelled()) {
doToast(getString(R.string.cancelled));
} else {
doToast(getString(R.string.import_complete));
}
} catch (IOException e) {
doToast(BookCatalogueApp.getResourceString(R.string.import_failed_is_location_correct));
Logger.logError(e);
} finally {
if (in != null && in.getChannel().isOpen())
try {
in.close();
} catch (IOException e) {
Logger.logError(e);
}
}
}
// if (export == null || export.size() == 0)
// return;
//
// mManager.setMax(this, export.size() - 1);
//
// // Container for values.
// BookData values = new BookData();
//
// String[] names = returnRow(export.get(0), true);
//
// // Store the names so we can check what is present
// for(int i = 0; i < names.length; i++) {
// names[i] = names[i].toLowerCase();
// values.putString(names[i], "");
// }
//
// // See if we can deduce the kind of escaping to use based on column names.
// // Version 1->3.3 export with family_name and author_id. Version 3.4+ do not; latest versions
// // make an attempt at escaping characters etc to preserve formatting.
// boolean fullEscaping;
// if (values.containsKey(CatalogueDBAdapter.KEY_AUTHOR_ID) && values.containsKey(CatalogueDBAdapter.KEY_FAMILY_NAME)) {
// // Old export, or one using old formats
// fullEscaping = false;
// } else {
// // More recent data format
// fullEscaping = true;
// }
//
// // Make sure required fields are present.
// // ENHANCE: Rationalize import to allow updates using 1 or 2 columns. For now we require complete data.
// // ENHANCE: Do a search if mandatory columns missing (eg. allow 'import' of a list of ISBNs).
// // ENHANCE: Only make some columns mandatory if the ID is not in import, or not in DB (ie. if not an update)
// // ENHANCE: Export/Import should use GUIDs for book IDs, and put GUIDs on Image file names.
// requireColumnOr(values, CatalogueDBAdapter.KEY_ROWID, DatabaseDefinitions.DOM_BOOK_UUID.name);
// requireColumnOr(values, CatalogueDBAdapter.KEY_FAMILY_NAME,
// CatalogueDBAdapter.KEY_AUTHOR_FORMATTED,
// CatalogueDBAdapter.KEY_AUTHOR_NAME,
// CatalogueDBAdapter.KEY_AUTHOR_DETAILS);
//
// int row = 1; // Start after headings.
// boolean inTx = false;
// int txRowCount = 0;
//
// long lastUpdate = 0;
// /* Iterate through each imported row */
// SyncLock txLock = null;
// try {
// while (row < export.size() && !isCancelled()) {
// if (inTx && txRowCount > 10) {
// mDbHelper.setTransactionSuccessful();
// mDbHelper.endTransaction(txLock);
// inTx = false;
// }
// if (!inTx) {
// txLock = mDbHelper.startTransaction(true);
// inTx = true;
// txRowCount = 0;
// }
// txRowCount++;
//
// // Get row
// String[] imported = returnRow(export.get(row), fullEscaping);
//
// values.clear();
// for(int i = 0; i < names.length; i++) {
// values.putString(names[i], imported[i]);
// }
//
// boolean hasNumericId;
// // Validate ID
// String idStr = values.getString(CatalogueDBAdapter.KEY_ROWID.toLowerCase());
// Long idLong;
// if (idStr == null || idStr == "") {
// hasNumericId = false;
// idLong = 0L;
// } else {
// try {
// idLong = Long.parseLong(idStr);
// hasNumericId = true;
// } catch (Exception e) {
// hasNumericId = false;
// idLong = 0L;
// }
// }
// if (!hasNumericId) {
// values.putString(CatalogueDBAdapter.KEY_ROWID, "0");
// }
//
// // Get the UUID, and remove from collection if null/blank
// boolean hasUuid;
// final String uuidColumnName = DatabaseDefinitions.DOM_BOOK_UUID.name.toLowerCase();
// String uuidVal = values.getString(uuidColumnName);
// if (uuidVal != null && !uuidVal.equals("")) {
// hasUuid = true;
// } else {
// // Remove any blank UUID column, just in case
// if (values.containsKey(uuidColumnName))
// values.remove(uuidColumnName);
// hasUuid = false;
// }
//
// requireNonblank(values, row, CatalogueDBAdapter.KEY_TITLE);
// String title = values.getString(CatalogueDBAdapter.KEY_TITLE);
//
// // Keep author handling stuff local
// {
// // Get the list of authors from whatever source is available.
// String authorDetails;
// authorDetails = values.getString(CatalogueDBAdapter.KEY_AUTHOR_DETAILS);
// if (authorDetails == null || authorDetails.length() == 0) {
// // Need to build it from other fields.
// if (values.containsKey(CatalogueDBAdapter.KEY_FAMILY_NAME)) {
// // Build from family/given
// authorDetails = values.getString(CatalogueDBAdapter.KEY_FAMILY_NAME);
// String given = "";
// if (values.containsKey(CatalogueDBAdapter.KEY_GIVEN_NAMES))
// given = values.getString(CatalogueDBAdapter.KEY_GIVEN_NAMES);
// if (given != null && given.length() > 0)
// authorDetails += ", " + given;
// } else if (values.containsKey(CatalogueDBAdapter.KEY_AUTHOR_NAME)) {
// authorDetails = values.getString(CatalogueDBAdapter.KEY_AUTHOR_NAME);
// } else if (values.containsKey(CatalogueDBAdapter.KEY_AUTHOR_FORMATTED)) {
// authorDetails = values.getString(CatalogueDBAdapter.KEY_AUTHOR_FORMATTED);
// }
// }
//
// if (authorDetails == null || authorDetails.length() == 0) {
// String s = BookCatalogueApp.getResourceString(R.string.column_is_blank);
// throw new ImportException(String.format(s, CatalogueDBAdapter.KEY_AUTHOR_DETAILS, row));
// }
//
// // Now build the array for authors
// ArrayList<Author> aa = Utils.getAuthorUtils().decodeList(authorDetails, '|', false);
// Utils.pruneList(mDbHelper, aa);
// values.putSerializable(CatalogueDBAdapter.KEY_AUTHOR_ARRAY, aa);
// }
//
// // Keep series handling local
// {
// String seriesDetails;
// seriesDetails = values.getString(CatalogueDBAdapter.KEY_SERIES_DETAILS);
// if (seriesDetails == null || seriesDetails.length() == 0) {
// // Try to build from SERIES_NAME and SERIES_NUM. It may all be blank
// if (values.containsKey(CatalogueDBAdapter.KEY_SERIES_NAME)) {
// seriesDetails = values.getString(CatalogueDBAdapter.KEY_SERIES_NAME);
// if (seriesDetails != null && seriesDetails.length() != 0) {
// String seriesNum = values.getString(CatalogueDBAdapter.KEY_SERIES_NUM);
// if (seriesNum == null)
// seriesNum = "";
// seriesDetails += "(" + seriesNum + ")";
// } else {
// seriesDetails = null;
// }
// }
// }
// // Handle the series
// ArrayList<Series> sa = Utils.getSeriesUtils().decodeList(seriesDetails, '|', false);
// Utils.pruneSeriesList(sa);
// Utils.pruneList(mDbHelper, sa);
// values.putSerializable(CatalogueDBAdapter.KEY_SERIES_ARRAY, sa);
// }
//
//
// // Make sure we have bookself_text if we imported bookshelf
// if (values.containsKey(CatalogueDBAdapter.KEY_BOOKSHELF) && !values.containsKey("bookshelf_text")) {
// values.setBookshelfList(values.getString(CatalogueDBAdapter.KEY_BOOKSHELF));
// }
//
// try {
// if (!hasUuid && !hasNumericId) {
// // Always import empty IDs...even if they are duplicates.
// Long id = mDbHelper.createBook(values);
// values.putString(CatalogueDBAdapter.KEY_ROWID, id.toString());
// // Would be nice to import a cover, but with no ID/UUID thats not possible
// //mImportCreated++;
// } else {
// boolean exists;
// // Save the original ID from the file for use in checing for images
// Long idFromFile = idLong;
// // newId will get the ID allocated if a book is created
// Long newId = 0L;
//
// // Let the UUID trump the ID; we may be importing someone else's list with bogus IDs
// if (hasUuid) {
// Long l = mDbHelper.getBookIdFromUuid(uuidVal);
// if (l != 0) {
// exists = true;
// idLong = l;
// } else {
// exists = false;
// // We have a UUID, but book does not exist. We will create a book.
// // Make sure the ID (if present) is not already used.
// if (hasNumericId && mDbHelper.checkBookExists(idLong))
// idLong = 0L;
// }
//
// } else {
// exists = mDbHelper.checkBookExists(idLong);
// }
//
// if (exists) {
// mDbHelper.updateBook(idLong, values, false);
// //mImportUpdated++;
// } else {
// newId = mDbHelper.createBook(idLong, values);
// //mImportCreated++;
// values.putString(CatalogueDBAdapter.KEY_ROWID, newId.toString());
// idLong = newId;
// }
// // When importing a file that has an ID or UUID, try to import a cover.
// if (hasUuid) {
// // Only copy UUID files if they are foreign...since they already exists, otherwise.
// if (mFileIsForeign)
// copyCoverImageIfMissing(uuidVal);
// } else {
// if (idFromFile != 0) {
// // This will be a rename or a copy
// if (mFileIsForeign)
// copyCoverImageIfMissing(idFromFile, idLong);
// else
// renameCoverImageIfMissing(idFromFile, idLong);
// }
// }
// }
// } catch (Exception e) {
// Logger.logError(e, "Import at row " + row);
// }
//
// if (values.containsKey(CatalogueDBAdapter.KEY_LOANED_TO) && !values.get(CatalogueDBAdapter.KEY_LOANED_TO).equals("")) {
// int id = Integer.parseInt(values.getString(CatalogueDBAdapter.KEY_ROWID));
// mDbHelper.deleteLoan(id);
// mDbHelper.createLoan(values);
// }
//
// if (values.containsKey(CatalogueDBAdapter.KEY_ANTHOLOGY_MASK)) {
// int anthology;
// try {
// anthology = Integer.parseInt(values.getString(CatalogueDBAdapter.KEY_ANTHOLOGY_MASK));
// } catch (Exception e) {
// anthology = 0;
// }
// if (anthology == CatalogueDBAdapter.ANTHOLOGY_MULTIPLE_AUTHORS || anthology == CatalogueDBAdapter.ANTHOLOGY_IS_ANTHOLOGY) {
// int id = Integer.parseInt(values.getString(CatalogueDBAdapter.KEY_ROWID));
// // We have anthology details, delete the current details.
// mDbHelper.deleteAnthologyTitles(id);
// int oldi = 0;
// String anthology_titles = values.getString("anthology_titles");
// try {
// int i = anthology_titles.indexOf("|", oldi);
// while (i > -1) {
// String extracted_title = anthology_titles.substring(oldi, i).trim();
//
// int j = extracted_title.indexOf("*");
// if (j > -1) {
// String anth_title = extracted_title.substring(0, j).trim();
// String anth_author = extracted_title.substring((j+1)).trim();
// mDbHelper.createAnthologyTitle(id, anth_author, anth_title, true);
// }
// oldi = i + 1;
// i = anthology_titles.indexOf("|", oldi);
// }
// } catch (NullPointerException e) {
// //do nothing. There are no anthology titles
// }
// }
// }
//
// long now = System.currentTimeMillis();
// if ( (now - lastUpdate) > 200 && !isCancelled()) {
// doProgress(title, row);
// lastUpdate = now;
// }
//
// // Increment row count
// row++;
// }
// } catch (Exception e) {
// Logger.logError(e);
// throw new RuntimeException(e);
// } finally {
// if (inTx) {
// mDbHelper.setTransactionSuccessful();
// mDbHelper.endTransaction(txLock);
// }
// mDbHelper.purgeAuthors();
// mDbHelper.purgeSeries();
// }
// try {
// mDbHelper.analyzeDb();
// } catch (Exception e) {
// // Do nothing. Not a critical step.
// Logger.logError(e);
// }
// if (isCancelled()) {
// doToast(getString(R.string.cancelled));
// } else {
// doToast(getString(R.string.import_complete));
// }
// }
// private File findExternalCover(String name) {
// // Find the original, if present.
// File orig = new File(mFile.getParent() + "/" + name + ".jpg");
// if (!orig.exists()) {
// orig = new File(mFile.getParent() + "/" + name + ".png");
// }
//
// // Nothing to copy?
// if (!orig.exists())
// return null;
// else
// return orig;
//
// }
//
// /**
// * Find the current cover file (or new file) based on the passed source and UUID.
// *
// * @param orig Original file to be copied/renamed if no existing file.
// * @param newUuid UUID of file
// *
// * @return Existing file (if length > 0), or new file object
// */
// private File getNewCoverFile(File orig, String newUuid) {
// File newFile;
// // Check for ANY current image; delete empty ones and retry
// newFile = CatalogueDBAdapter.fetchThumbnailByUuid(newUuid);
// while (newFile.exists()) {
// if (newFile.length() > 0)
// return newFile;
// else
// newFile.delete();
// newFile = CatalogueDBAdapter.fetchThumbnailByUuid(newUuid);
// }
//
// // Get the new path based on the input file type.
// if (orig.getAbsolutePath().toLowerCase().endsWith(".png"))
// newFile = new File(mSharedStoragePath + "/" + newUuid + ".png");
// else
// newFile = new File(mSharedStoragePath + "/" + newUuid + ".jpg");
//
// return newFile;
// }
// /**
// * Copy a specified source file into the default cover location for a new file.
// * DO NO OVERWRITE EXISTING FILES.
// *
// * @param orig
// * @param newUuid
// * @throws IOException
// */
// private void copyFileToCoverImageIfMissing(File orig, String newUuid) throws IOException {
// // Nothing to copy?
// if (orig == null || !orig.exists() || orig.length() == 0)
// return;
//
// // Check for ANY current image
// File newFile = getNewCoverFile(orig, newUuid);
// if (newFile.exists())
// return;
//
// // Copy it.
// InputStream in = null;
// OutputStream out = null;
// try {
// // Open in & out
// in = new FileInputStream(orig);
// out = new FileOutputStream(newFile);
// // Get a buffer
// byte[] buffer = new byte[8192];
// int nRead = 0;
// // Copy
// while( (nRead = in.read(buffer)) > 0){
// out.write(buffer, 0, nRead);
// }
// // Close both. We close them here so exceptions are signalled
// in.close();
// in = null;
// out.close();
// out = null;
// } finally {
// // If not already closed, close.
// try {
// if (in != null)
// in.close();
// } catch (Exception e) {};
// try {
// if (out != null)
// out.close();
// } catch (Exception e) {};
// }
// }
//
// /**
// * Rename/move a specified source file into the default cover location for a new file.
// * DO NO OVERWRITE EXISTING FILES.
// *
// * @param orig
// * @param newUuid
// * @throws IOException
// */
// private void renameFileToCoverImageIfMissing(File orig, String newUuid) throws IOException {
// // Nothing to copy?
// if (orig == null || !orig.exists() || orig.length() == 0)
// return;
//
// // Check for ANY current image
// File newFile = getNewCoverFile(orig, newUuid);
// if (newFile.exists())
// return;
//
// orig.renameTo(newFile);
// }
//
// /**
// * Copy the ID-based cover from its current location to the correct location in shared
// * storage, if it exists.
// *
// * @param externalId The file ID in external media
// * @param newId The new file ID
// * @throws IOException
// */
// private void renameCoverImageIfMissing(long externalId, long newId) throws IOException {
// File orig = findExternalCover(Long.toString(externalId));
// // Nothing to copy?
// if (orig == null || !orig.exists() || orig.length() == 0)
// return;
//
// String newUuid = mDbHelper.getBookUuid(newId);
//
// renameFileToCoverImageIfMissing(orig, newUuid);
// }
//
// /**
// * Copy the ID-based cover from its current location to the correct location in shared
// * storage, if it exists.
// *
// * @param externalId The file ID in external media
// * @param newId The new file ID
// * @throws IOException
// */
// private void copyCoverImageIfMissing(long externalId, long newId) throws IOException {
// File orig = findExternalCover(Long.toString(externalId));
// // Nothing to copy?
// if (orig == null || !orig.exists() || orig.length() == 0)
// return;
//
// String newUuid = mDbHelper.getBookUuid(newId);
//
// copyFileToCoverImageIfMissing(orig, newUuid);
// }
//
// /**
// * Copy the UUID-based cover from its current location to the correct location in shared
// * storage, if it exists.
// *
// * @param uuid
// * @throws IOException
// */
// private void copyCoverImageIfMissing(String uuid) throws IOException {
// File orig = findExternalCover(uuid);
// // Nothing to copy?
// if (orig == null || !orig.exists())
// return;
//
// copyFileToCoverImageIfMissing(orig, uuid);
// }
//
// private final static char QUOTE_CHAR = '"';
// private final static char ESCAPE_CHAR = '\\';
// private final static char SEPARATOR = ',';
// private char unescape(char c) {
// switch(c) {
// case 'r':
// return '\r';
// case 't':
// return '\t';
// case 'n':
// return '\n';
// default:
// // Handle simple escapes. We could go further and allow arbitrary numeric wchars by
// // testing for numeric sequences here but that is beyond the scope of this app.
// return c;
// }
// }
// //
// // This CSV parser is not a complete parser, but it will parse files exported by older
// // versions. At some stage in the future it would be good to allow full CSV export
// // and import to allow for escape('\') chars so that cr/lf can be preserved.
// //
// private String[] returnRow(String row, boolean fullEscaping) {
// // Need to handle double quotes etc
// int pos = 0; // Current position
// boolean inQuote = false; // In a quoted string
// boolean inEsc = false; // Found an escape char
// char c; // 'Current' char
// char next // 'Next' char
// = (row.length() > 0) ? row.charAt(0) : '\0';
// int endPos // Last position in row
// = row.length() - 1;
// ArrayList<String> fields // Array of fields found in row
// = new ArrayList<String>();
//
// StringBuilder bld // Temp. storage for current field
// = new StringBuilder();
//
// while (next != '\0')
// {
// // Get current and next char
// c = next;
// next = (pos < endPos) ? row.charAt(pos+1) : '\0';
//
// // If we are 'escaped', just append the char, handling special cases
// if (inEsc) {
// bld.append(unescape(c));
// inEsc = false;
// }
// else if (inQuote)
// {
// switch(c) {
// case QUOTE_CHAR:
// if (next == QUOTE_CHAR)
// {
// // Double-quote: Advance one more and append a single quote
// pos++;
// next = (pos < endPos) ? row.charAt(pos+1) : '\0';
// bld.append(c);
// } else {
// // Leave the quote
// inQuote = false;
// }
// break;
// case ESCAPE_CHAR:
// if (fullEscaping)
// inEsc = true;
// else
// bld.append(c);
// break;
// default:
// bld.append(c);
// break;
// }
// } else {
// // This is just a raw string; no escape or quote active.
// // Ignore leading space.
// if ((c == ' ' || c == '\t') && bld.length() == 0 ) {
// // Skip leading white space
// } else {
// switch(c){
// case QUOTE_CHAR:
// if (bld.length() > 0) {
// // Fields with quotes MUST be quoted...
// throw new IllegalArgumentException();
// } else {
// inQuote = true;
// }
// break;
// case ESCAPE_CHAR:
// if (fullEscaping)
// inEsc = true;
// else
// bld.append(c);
// break;
// case SEPARATOR:
// // Add this field and reset it.
// fields.add(bld.toString());
// bld = new StringBuilder();
// break;
// default:
// // Just append the char
// bld.append(c);
// break;
// }
// }
// }
// pos++;
// };
//
// // Add the remaining chunk
// fields.add(bld.toString());
//
// // Return the result as a String[].
// String[] imported = new String[fields.size()];
// fields.toArray(imported);
//
// return imported;
// }
//
// // Require a column
// @SuppressWarnings("unused")
// private void requireColumn(Bundle values, String name) {
// if (values.containsKey(name))
// return;
//
// String s = BookCatalogueApp.getResourceString(R.string.file_must_contain_column);
// throw new ImportException(String.format(s,name));
// }
//
// // Require a column
// private void requireColumnOr(BookData values, String... names) {
// for(int i = 0; i < names.length; i++)
// if (values.containsKey(names[i]))
// return;
//
// String s = BookCatalogueApp.getResourceString(R.string.file_must_contain_any_column);
// throw new ImportException(String.format(s, Utils.join(names, ",")));
// }
//
// private void requireNonblank(BookData values, int row, String name) {
// if (values.getString(name).length() != 0)
// return;
// String s = BookCatalogueApp.getResourceString(R.string.column_is_blank);
// throw new ImportException(String.format(s, name, row));
// }
//
// @SuppressWarnings("unused")
// private void requireAnyNonblank(BookData values, int row, String... names) {
// for(int i = 0; i < names.length; i++)
// if (values.containsKey(names[i]) && values.getString(names[i]).length() != 0)
// return;
//
// String s = BookCatalogueApp.getResourceString(R.string.columns_are_blank);
// throw new ImportException(String.format(s, Utils.join( names, ","), row));
// }
/**
* Cleanup any DB connection etc after main task has run.
*/
private void cleanup() {
if (mDbHelper != null) {
mDbHelper.close();
mDbHelper = null;
}
}
@Override
protected void finalize() throws Throwable {
cleanup();
super.finalize();
}
}