/* * @copyright 2013 Evan Leybourn * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue 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. * * Book Catalogue 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 Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue.backup; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.Date; import android.database.Cursor; import com.eleybourn.bookcatalogue.BookCatalogueApp; import com.eleybourn.bookcatalogue.BookEditFields; import com.eleybourn.bookcatalogue.BooksCursor; import com.eleybourn.bookcatalogue.BooksRowView; import com.eleybourn.bookcatalogue.CatalogueDBAdapter; import com.eleybourn.bookcatalogue.R; import com.eleybourn.bookcatalogue.booklist.DatabaseDefinitions; import com.eleybourn.bookcatalogue.utils.Logger; import com.eleybourn.bookcatalogue.utils.StorageUtils; import com.eleybourn.bookcatalogue.utils.Utils; /** * Implementation of Exporter that creates a CSV file. * * @author pjw */ public class CsvExporter implements Exporter { private String mLastError; private static String UTF8 = "utf8"; private static int BUFFER_SIZE = 32768; public String getLastError() { return mLastError; } public boolean export(OutputStream outputStream, Exporter.ExportListener listener, final int backupFlags, Date since) throws IOException { final String UNKNOWN = BookCatalogueApp.getResourceString(R.string.unknown); final String AUTHOR = BookCatalogueApp.getResourceString(R.string.author); /** RELEASE: Handle flags! */ int num = 0; if (!StorageUtils.sdCardWritable()) { mLastError = "Export Failed - Could not write to SDCard"; return false; } // Fix the 'since' date, if required if ( (backupFlags & Exporter.EXPORT_SINCE) != 0) { if (since == null) { mLastError = "Export Failed - 'since' is null"; return false; } } else { since = null; } // Display startup message listener.onProgress(BookCatalogueApp.getResourceString(R.string.export_starting_ellipsis), 0); boolean displayingStartupMessage = true; StringBuilder export = new StringBuilder( '"' + CatalogueDBAdapter.KEY_ROWID + "\"," + //0 '"' + CatalogueDBAdapter.KEY_AUTHOR_DETAILS + "\"," + //2 '"' + CatalogueDBAdapter.KEY_TITLE + "\"," + //4 '"' + CatalogueDBAdapter.KEY_ISBN + "\"," + //5 '"' + CatalogueDBAdapter.KEY_PUBLISHER + "\"," + //6 '"' + CatalogueDBAdapter.KEY_DATE_PUBLISHED + "\"," + //7 '"' + CatalogueDBAdapter.KEY_RATING + "\"," + //8 '"' + "bookshelf_id\"," + //9 '"' + CatalogueDBAdapter.KEY_BOOKSHELF + "\"," + //10 '"' + CatalogueDBAdapter.KEY_READ + "\"," + //11 '"' + CatalogueDBAdapter.KEY_SERIES_DETAILS + "\"," + //12 '"' + CatalogueDBAdapter.KEY_PAGES + "\"," + //14 '"' + CatalogueDBAdapter.KEY_NOTES + "\"," + //15 '"' + CatalogueDBAdapter.KEY_LIST_PRICE + "\"," + //16 '"' + CatalogueDBAdapter.KEY_ANTHOLOGY_MASK+ "\"," + //17 '"' + CatalogueDBAdapter.KEY_LOCATION+ "\"," + //18 '"' + CatalogueDBAdapter.KEY_READ_START+ "\"," + //19 '"' + CatalogueDBAdapter.KEY_READ_END+ "\"," + //20 '"' + CatalogueDBAdapter.KEY_FORMAT+ "\"," + //21 '"' + CatalogueDBAdapter.KEY_SIGNED+ "\"," + //22 '"' + CatalogueDBAdapter.KEY_LOANED_TO+ "\"," + //23 '"' + "anthology_titles" + "\"," + //24 '"' + CatalogueDBAdapter.KEY_DESCRIPTION+ "\"," + //25 '"' + CatalogueDBAdapter.KEY_GENRE+ "\"," + //26 '"' + DatabaseDefinitions.DOM_LANGUAGE+ "\"," + //+1 '"' + CatalogueDBAdapter.KEY_DATE_ADDED+ "\"," + //27 '"' + DatabaseDefinitions.DOM_GOODREADS_BOOK_ID + "\"," + //28 '"' + DatabaseDefinitions.DOM_LAST_GOODREADS_SYNC_DATE + "\"," + //29 '"' + DatabaseDefinitions.DOM_LAST_UPDATE_DATE + "\"," + //30 '"' + DatabaseDefinitions.DOM_BOOK_UUID + "\"," + //31 "\n"); long lastUpdate = 0; StringBuilder row = new StringBuilder(); CatalogueDBAdapter db; db = new CatalogueDBAdapter(BookCatalogueApp.context); db.open(); BooksCursor books = db.exportBooks(since); BooksRowView rv = books.getRowView(); try { final int totalBooks = books.getCount(); if (!listener.isCancelled()) { listener.setMax(totalBooks); /* write to the SDCard */ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outputStream, UTF8), BUFFER_SIZE); out.write(export.toString()); if (books.moveToFirst()) { do { num++; long id = books.getLong(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ROWID)); // Just get the string from the database and save it. It should be in standard SQL form already. String dateString = ""; try { dateString = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_DATE_PUBLISHED)); } catch (Exception e) { //do nothing } // Just get the string from the database and save it. It should be in standard SQL form already. String dateReadStartString = ""; try { dateReadStartString = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_READ_START)); } catch (Exception e) { Logger.logError(e); //do nothing } // Just get the string from the database and save it. It should be in standard SQL form already. String dateReadEndString = ""; try { dateReadEndString = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_READ_END)); } catch (Exception e) { Logger.logError(e); //do nothing } // Just get the string from the database and save it. It should be in standard SQL form already. String dateAddedString = ""; try { dateAddedString = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_DATE_ADDED)); } catch (Exception e) { //do nothing } int anthology = books.getInt(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ANTHOLOGY_MASK)); String anthology_titles = ""; if (anthology != 0) { Cursor titles = db.fetchAnthologyTitlesByBook(id); try { if (titles.moveToFirst()) { do { String anth_title = titles.getString(titles.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_TITLE)); String anth_author = titles.getString(titles.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_AUTHOR_NAME)); anthology_titles += anth_title + " * " + anth_author + "|"; } while (titles.moveToNext()); } } finally { if (titles != null) titles.close(); } } String title = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_TITLE)); // Sanity check: ensure title is non-blank. This has not happened yet, but we // know if does for author, so completeness suggests making sure all 'required' // fields are non-blank. if (title == null || title.trim().equals("")) title = UNKNOWN; //Display the selected bookshelves Cursor bookshelves = db.fetchAllBookshelvesByBook(id); String bookshelves_id_text = ""; String bookshelves_name_text = ""; while (bookshelves.moveToNext()) { bookshelves_id_text += bookshelves.getString(bookshelves.getColumnIndex(CatalogueDBAdapter.KEY_ROWID)) + BookEditFields.BOOKSHELF_SEPERATOR; bookshelves_name_text += Utils.encodeListItem(bookshelves.getString(bookshelves.getColumnIndex(CatalogueDBAdapter.KEY_BOOKSHELF)),BookEditFields.BOOKSHELF_SEPERATOR) + BookEditFields.BOOKSHELF_SEPERATOR; } bookshelves.close(); String authorDetails = Utils.getAuthorUtils().encodeList( db.getBookAuthorList(id), '|' ); // Sanity check: ensure author is non-blank. This HAPPENS. Probably due to constraint failures. if (authorDetails == null || authorDetails.trim().equals("")) authorDetails = AUTHOR + ", " + UNKNOWN; String seriesDetails = Utils.getSeriesUtils().encodeList( db.getBookSeriesList(id), '|' ); row.setLength(0); row.append("\"" + formatCell(id) + "\","); row.append("\"" + formatCell(authorDetails) + "\","); row.append( "\"" + formatCell(title) + "\"," ); row.append("\"" + formatCell(rv.getIsbn()) + "\","); row.append("\"" + formatCell(rv.getPublisher()) + "\","); row.append("\"" + formatCell(dateString) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_RATING))) + "\","); row.append("\"" + formatCell(bookshelves_id_text) + "\","); row.append("\"" + formatCell(bookshelves_name_text) + "\","); row.append("\"" + formatCell(rv.getRead()) + "\","); row.append("\"" + formatCell(seriesDetails) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_PAGES))) + "\","); row.append("\"" + formatCell(rv.getNotes()) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_LIST_PRICE))) + "\","); row.append("\"" + formatCell(anthology) + "\","); row.append("\"" + formatCell(rv.getLocation()) + "\","); row.append("\"" + formatCell(dateReadStartString) + "\","); row.append("\"" + formatCell(dateReadEndString) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_FORMAT))) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_SIGNED))) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_LOANED_TO))+"") + "\","); row.append("\"" + formatCell(anthology_titles) + "\","); row.append("\"" + formatCell(rv.getDescription()) + "\","); row.append("\"" + formatCell(rv.getGenre()) + "\","); row.append("\"" + formatCell(rv.getLanguage()) + "\","); row.append("\"" + formatCell(dateAddedString) + "\","); row.append("\"" + formatCell(rv.getGoodreadsBookId()) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(DatabaseDefinitions.DOM_LAST_GOODREADS_SYNC_DATE.name))) + "\","); row.append("\"" + formatCell(books.getString(books.getColumnIndexOrThrow(DatabaseDefinitions.DOM_LAST_UPDATE_DATE.name))) + "\","); row.append("\"" + formatCell(rv.getBookUuid()) + "\","); row.append("\n"); out.write(row.toString()); //export.append(row); long now = System.currentTimeMillis(); if ( (now - lastUpdate) > 200) { if (displayingStartupMessage) { listener.onProgress("",0); displayingStartupMessage = false; } listener.onProgress(title, num); lastUpdate = now; } } while (books.moveToNext() && !listener.isCancelled()); } out.close(); } } finally { System.out.println("Books Exported: " + num); if (displayingStartupMessage) try { listener.onProgress("",0); displayingStartupMessage = false; } catch (Exception e) { } if (books != null) try { books.close(); } catch (Exception e) {}; if (db != null) db.close(); } return true; } /** * Double quote all "'s and remove all newlines * * @param cell The cell the format * @return The formatted cell */ private String formatCell(String cell) { try { if (cell.equals("null") || cell.trim().length() == 0) { return ""; } StringBuilder bld = new StringBuilder(); int endPos = cell.length() - 1; int pos = 0; while (pos <= endPos) { char c = cell.charAt(pos); switch(c) { case '\r': bld.append("\\r"); break; case '\n': bld.append("\\n"); break; case '\t': bld.append("\\t"); break; case '"': bld.append("\"\""); break; case '\\': bld.append("\\\\"); break; default: bld.append(c); } pos++; } return bld.toString(); } catch (NullPointerException e) { return ""; } } /** * @see #formatCell(String) * @param cell The cell the format * @return The formatted cell */ private String formatCell(long cell) { String newcell = cell + ""; return formatCell(newcell); } }