/* * @copyright 2010 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 . */ package com.eleybourn.bookcatalogue; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; import android.widget.Toast; /** * * This is the Administration page. It contains details about the app, links * to my website and email, functions to export and import books and functions to * manage bookshelves. * * @author Evan Leybourn */ public class AdministrationFunctions extends Activity { private static final int ACTIVITY_BOOKSHELF=1; private static final int ACTIVITY_FIELD_VISIBILITY=2; private CatalogueDBAdapter mDbHelper; private int importUpdated = 0; private int importCreated = 0; public static String filePath = Environment.getExternalStorageDirectory() + "/" + BookCatalogue.LOCATION; public static String fileName = filePath + "/export.csv"; public static String UTF8 = "utf8"; public static int BUFFER_SIZE = 8192; private ProgressDialog pd = null; private int num = 0; private boolean finish_after = false; public static final String DOAUTO = "do_auto"; final Handler handler = new Handler() { public void handleMessage(Message msg) { int total = msg.getData().getInt("total"); String title = msg.getData().getString("title"); if (total == 0) { pd.dismiss(); if (finish_after == true) { finish(); } Toast.makeText(AdministrationFunctions.this, title, Toast.LENGTH_LONG).show(); //progressThread.setState(UpdateThumbnailsThread.STATE_DONE); } else { num += 1; pd.incrementProgressBy(1); if (title.length() > 21) { title = title.substring(0, 20) + "..."; } pd.setMessage(title); } } }; /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { try { super.onCreate(savedInstanceState); mDbHelper = new CatalogueDBAdapter(this); mDbHelper.open(); setContentView(R.layout.administration_functions); Bundle extras = getIntent().getExtras(); try { if (extras.getString(DOAUTO).equals("export")) { finish_after = true; exportData(); } else if (extras.getString(DOAUTO).equals("update_fields")) { finish_after = true; updateThumbnails(false); } } catch (NullPointerException e) { //do nothing } setupAdmin(); } catch (Exception e) { //Log.e("Book Catalogue", "Unknown Exception - BC onCreate - " + e.getMessage() ); } } /** * This function builds the Administration page in 4 sections. * 1. The button to goto the manage bookshelves activity * 2. The button to export the database * 3. The button to import the exported file into the database * 4. The application version and link details * 5. The link to paypal for donation */ public void setupAdmin() { /* Bookshelf Link */ TextView bookshelf = (TextView) findViewById(R.id.bookshelf_label); bookshelf.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { manageBookselves(); return; } }); /* Manage Fields Link */ TextView fields = (TextView) findViewById(R.id.fields_label); fields.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { manageFields(); return; } }); /* Export Link */ TextView export = (TextView) findViewById(R.id.export_label); export.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { exportData(); return; } }); /* Import Link */ TextView imports = (TextView) findViewById(R.id.import_label); /* Hack to pass this into the class */ final AdministrationFunctions pthis = this; imports.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Verify - this can be a dangerous operation AlertDialog alertDialog = new AlertDialog.Builder(pthis).setMessage(R.string.import_alert).create(); alertDialog.setTitle(R.string.import_data); alertDialog.setIcon(android.R.drawable.ic_menu_info_details); alertDialog.setButton(pthis.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { importData(); //Toast.makeText(pthis, importUpdated + " Existing, " + importCreated + " Created", Toast.LENGTH_LONG).show(); return; } }); alertDialog.setButton2(pthis.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //do nothing return; } }); alertDialog.show(); return; } }); // // Debug ONLY! // /* Backup Link */ // TextView backup = (TextView) findViewById(R.id.backup_label); // backup.setOnClickListener(new OnClickListener() { // @Override // public void onClick(View v) { // mDbHelper.backupDbFile(); // return; // } // }); /* Export Link */ TextView thumb = (TextView) findViewById(R.id.thumb_label); thumb.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Verify - this can be a dangerous operation AlertDialog alertDialog = new AlertDialog.Builder(pthis).setMessage(R.string.overwrite_thumbnail).create(); alertDialog.setTitle(R.string.update_thumbnails); alertDialog.setIcon(android.R.drawable.ic_menu_info_details); alertDialog.setButton(pthis.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { updateThumbnails(true); return; } }); alertDialog.setButton2(pthis.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //do nothing return; } }); alertDialog.setButton3(pthis.getResources().getString(R.string.no), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { updateThumbnails(false); return; } }); alertDialog.show(); return; } }); } /** * Load the Bookshelf Activity */ private void manageBookselves() { Intent i = new Intent(this, Bookshelf.class); startActivityForResult(i, ACTIVITY_BOOKSHELF); } /** * Load the Manage Field Visibility Activity */ private void manageFields() { Intent i = new Intent(this, FieldVisibility.class); startActivityForResult(i, ACTIVITY_FIELD_VISIBILITY); } private class UpdateThumbnailsThread extends Thread { public boolean overwrite = false; public Cursor books = null; private Handler mHandler; /** * @param handler */ public UpdateThumbnailsThread(Handler handler) { mHandler = handler; } private void sendMessage(int num, String title) { /* Send message to the handler */ Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putInt("total", num); b.putString("title", title); msg.setData(b); mHandler.sendMessage(msg); return; } @Override public void run() { Looper.prepare(); /* Test write to the SDCard */ try { BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath + "/.nomedia"), UTF8), BUFFER_SIZE); out.write(""); out.close(); } catch (IOException e) { sendMessage(0, "Thumbnail Download Failed - Could not write to SDCard"); return; } startManagingCursor(books); int num = 0; //try { while (books.moveToNext()) { long id = books.getLong(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ROWID)); String isbn = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ISBN)); String title = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_TITLE)); String genre = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_GENRE)); String description = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_DESCRIPTION)); try { genre.equals(""); } catch (NullPointerException e) { genre = ""; } try { description.equals(""); } catch (NullPointerException e) { description = ""; } String author = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_AUTHOR_FORMATTED)); String publisher = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_PUBLISHER)); String date_published = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_DATE_PUBLISHED)); String series = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_SERIES)); String series_num = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_SERIES_NUM)); String list_price = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_LIST_PRICE)); int pages = books.getInt(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_PAGES)); String format = books.getString(books.getColumnIndex(CatalogueDBAdapter.KEY_FORMAT)); int anthology = books.getInt(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_ANTHOLOGY)); float rating = books.getFloat(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_RATING)); //Display the selected bookshelves Cursor bookshelves = mDbHelper.fetchAllBookshelvesByBook(id); String bookshelf = ""; while (bookshelves.moveToNext()) { bookshelf += bookshelves.getString(bookshelves.getColumnIndex(CatalogueDBAdapter.KEY_BOOKSHELF)) + BookEditFields.BOOKSHELF_SEPERATOR; } boolean read = (books.getInt(books.getColumnIndex(CatalogueDBAdapter.KEY_READ))==0 ? false:true); String notes = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_NOTES)); String location = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_LOCATION)); String read_start = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_READ_START)); String read_end = books.getString(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_READ_END)); boolean signed = (books.getInt(books.getColumnIndexOrThrow(CatalogueDBAdapter.KEY_SIGNED))==0 ? false:true); num++; // delete any tmp thumbnails // try { File delthumb = CatalogueDBAdapter.fetchThumbnail(0); delthumb.delete(); } catch (Exception e) { // do nothing - this is the expected behaviour } String[] book = null; File thumb = CatalogueDBAdapter.fetchThumbnail(id); if (isbn.equals("") && author.equals("") && title.equals("")) { // Must have an ISBN to be able to search sendMessage(num, "Skip - " + title); //TODO: searchGoogle(AUTHOR) } else if (overwrite == true || !thumb.exists() || genre.equals("") || description.equals("")) { sendMessage(num, title); BookISBNSearch bis = new BookISBNSearch(); //String[] book = {0=author, 1=title, 2=isbn, 3=publisher, 4=date_published, 5=rating, 6=bookshelf, // 7=read, 8=series, 9=pages, 10=series_num, 11=list_price, 12=anthology, 13=location, 14=read_start, // 15=read_end, 16=audiobook, 17=signed, 18=description, 19=genre}; book = bis.searchGoogle(isbn, author, title); File tmpthumb = CatalogueDBAdapter.fetchThumbnail(0); String[] bookAmazon = bis.searchAmazon(isbn, author, title); tmpthumb = CatalogueDBAdapter.fetchThumbnail(0); /* Fill blank fields as required */ for (int i = 0; i readFile() { ArrayList importedString = new ArrayList(); try { BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), UTF8),BUFFER_SIZE); String line = ""; while ((line = in.readLine()) != null) { importedString.add(line); } in.close(); } catch (FileNotFoundException e) { Toast.makeText(this, R.string.import_failed, Toast.LENGTH_LONG).show(); } catch (IOException e) { Toast.makeText(this, R.string.import_failed, Toast.LENGTH_LONG).show(); } return importedString; } private class ImportThread extends Thread { public ArrayList export = null; private Handler mHandler; /** * @param handler */ public ImportThread(Handler handler) { mHandler = handler; } private void sendMessage(int num, String title) { /* Send message to the handler */ Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putInt("total", num); b.putString("title", title); msg.setData(b); mHandler.sendMessage(msg); return; } // // 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) { // Need to handle double quotes etc char sep = ','; // CSV seperator char quoteChar = '"'; // CSV quote char int pos = 0; // Current position boolean inQuote = false; // In a quoted string 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 fields // Array of fields found in row = new ArrayList(); 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 (inQuote) { if (c == quoteChar) { if (next == quoteChar) { // 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; } } else { // Append anything else that appears in quotes bld.append(c); } } else { if (bld.length() == 0 && (c == ' ' || c == '\t') ) { // Skip leading white space } else if (c == quoteChar) { if (bld.length() > 0) { // Fields with quotes MUST be quoted... throw new IllegalArgumentException(); } else { inQuote = true; } } else if (c == sep) { // Add this field and reset it. fields.add(bld.toString()); bld = new StringBuilder(); } else { // Just append the char bld.append(c); } } 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; } @Override public void run() { Looper.prepare(); int row = 1; int num = 0; /* Iterate through each imported row */ while (row < export.size()) { num++; String[] imported = returnRow(export.get(row)); row++; /* Setup aliases for each cell*/ Long id = null; try { id = Long.parseLong(imported[0]); } catch(Exception e) { id = Long.parseLong("0"); } String family = ""; try { family = imported[1]; } catch (Exception e) { // family is a compulsory field continue; } String given = ""; try { given = imported[2]; } catch (Exception e) { given = ""; } //String author_id = imported[3]; String title = ""; try { title = imported[4]; } catch (Exception e) { //title is a compulsory field continue; } String isbn = ""; try { isbn = imported[5]; } catch (Exception e) { isbn = ""; } String publisher = ""; try { publisher = imported[6]; } catch (Exception e) { publisher = ""; } String date_published = ""; try { date_published = imported[7]; String[] date = date_published.split("-"); int yyyy = Integer.parseInt(date[0]); int mm = Integer.parseInt(date[1])-1; int dd = Integer.parseInt(date[2]); date_published = yyyy + "-" + mm + "-" + dd; } catch (Exception e) { date_published = ""; } float rating = 0; try { rating = Float.valueOf(imported[8]); } catch (Exception e) { rating = 0; } //String bookshelf_id = imported[9]; String bookshelf = ""; try { bookshelf = imported[10]; } catch (Exception e) { bookshelf = ""; } boolean read = false; try { read = (imported[11].equals("0")? false:true); } catch (Exception e) { read = false; } String series = ""; try { series = imported[12]; } catch (Exception e) { series = ""; } String series_num = ""; try { series_num = imported[13]; } catch (Exception e) { series_num = ""; } int pages = 0; try { pages = Integer.parseInt(imported[14]); } catch (Exception e) { pages = 0; } String notes = ""; try { notes = imported[15]; } catch (Exception e) { notes = ""; } String list_price = ""; try { list_price = imported[16]; } catch (ArrayIndexOutOfBoundsException e) { list_price = ""; } int anthology = CatalogueDBAdapter.ANTHOLOGY_NO; try { anthology = Integer.parseInt(imported[17]); } catch (Exception e) { anthology = 0; } String location = ""; try { location = imported[18]; } catch (Exception e) { location = ""; } String read_start = ""; try { read_start = imported[19]; String[] date = read_start.split("-"); int yyyy = Integer.parseInt(date[0]); int mm = Integer.parseInt(date[1])-1; int dd = Integer.parseInt(date[2]); read_start = yyyy + "-" + mm + "-" + dd; } catch (Exception e) { read_start = ""; } String read_end = ""; try { read_end = imported[20]; Log.e("BC", "R" + read_end); String[] date = read_end.split("-"); int yyyy = Integer.parseInt(date[0]); int mm = Integer.parseInt(date[1])-1; int dd = Integer.parseInt(date[2]); read_end = yyyy + "-" + mm + "-" + dd; Log.e("BC", "C" + read_end); } catch (Exception e) { read_end = ""; Log.e("BC", "ERROR"); } String format = ""; try { format = imported[21]; } catch (Exception e) { format = ""; } boolean signed = false; try { signed = (imported[22].equals("0")? false:true); } catch (Exception e) { signed = false; } String loan = ""; try { loan = imported[23]; } catch (Exception e) { loan = ""; } String anthology_titles = ""; try { anthology_titles = imported[24]; } catch (Exception e) { anthology_titles = ""; } String description = ""; try { description = imported[25]; } catch (Exception e) { description = ""; } String genre = ""; try { genre = imported[26]; } catch (Exception e) { genre = ""; } String author = family + ", " + given; try { if (id == 0) { // ID is unknown, may be new. Check if it exists in the current database. Cursor book = null; int rows = 0; // If the ISBN is specified, use it as a definitive lookup. if (isbn != "") { book = mDbHelper.fetchBookByISBN(isbn); rows = book.getCount(); } else { if (rows == 0) { book = mDbHelper.fetchByAuthorAndTitle(family, given, title); rows = book.getCount(); } } if (rows != 0) { book.moveToFirst(); // Its a new entry, but the ISBN exists id = book.getLong(0); book.moveToFirst(); mDbHelper.updateBook(id, author, title, isbn, publisher, date_published, rating, bookshelf, read, series, pages, series_num, notes, list_price, anthology, location, read_start, read_end, format, signed, description, genre); importUpdated++; } else { id = mDbHelper.createBook(author, title, isbn, publisher, date_published, rating, bookshelf, read, series, pages, series_num, notes, list_price, anthology, location, read_start, read_end, format, signed, description, genre); importCreated++; } } else { Cursor book = mDbHelper.fetchBookById(id); int rows = book.getCount(); if (rows == 0) { mDbHelper.createBook(id, author, title, isbn, publisher, date_published, rating, bookshelf, read, series, pages, series_num, notes, list_price, anthology, location, read_start, read_end, format, signed, description, genre); importCreated++; } else { // Book exists and should be updated if it has changed mDbHelper.updateBook(id, author, title, isbn, publisher, date_published, rating, bookshelf, read, series, pages, series_num, notes, list_price, anthology, location, read_start, read_end, format, signed, description, genre); importUpdated++; } } } catch (Exception e) { //Log.e("BC", "Import Book (Single) Error"); // do nothing } if (!loan.equals("")) { mDbHelper.createLoan(id, loan); } if (anthology == CatalogueDBAdapter.ANTHOLOGY_MULTIPLE_AUTHORS || anthology == CatalogueDBAdapter.ANTHOLOGY_SAME_AUTHOR) { int oldi = 0; 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); } oldi = i + 1; i = anthology_titles.indexOf("|", oldi); } } sendMessage(num, title); } sendMessage(0, "Import Complete"); } } /** * Import all data from the CSV file * * return void */ private void importData() { importUpdated = 0; importCreated = 0; ArrayList export = readFile(); pd = new ProgressDialog(AdministrationFunctions.this); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMessage("Importing..."); pd.setCancelable(false); pd.setMax(export.size() - 1); pd.show(); ImportThread thread = new ImportThread(handler); thread.export = export; thread.start(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); switch(requestCode) { case ACTIVITY_BOOKSHELF: case ACTIVITY_FIELD_VISIBILITY: //do nothing (yet) break; } } @Override protected void onDestroy() { super.onDestroy(); mDbHelper.close(); } }