/*
* @copyright 2012 Philip Warner
* @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;
import java.util.ArrayList;
import net.philipwarner.taskqueue.BindableItemSQLiteCursor;
import net.philipwarner.taskqueue.ContextDialogItem;
import net.philipwarner.taskqueue.Event;
import net.philipwarner.taskqueue.EventsCursor;
import net.philipwarner.taskqueue.QueueManager;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDoneException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import com.eleybourn.bookcatalogue.goodreads.SendOneBookTask;
import com.eleybourn.bookcatalogue.utils.HintManager.HintOwner;
import com.eleybourn.bookcatalogue.utils.Utils;
import com.eleybourn.bookcatalogue.utils.ViewTagger;
/**
* Class to define all book-related events that may be stored in the QueueManager.
*
* *************!!!!!!!!!!!!*************!!!!!!!!!!!!*************!!!!!!!!!!!!*************
* NOTE: If internal data or data types need to change in this class after the application
* has been deployed, CREATE A NEW CLASS OR EVENT. Otherwise, deployed serialized objects
* may fail to reload. This is not a big issue (it's what LegacyTask and LegacyEvent are
* for), but it is better to present data to the users cleanly.
* *************!!!!!!!!!!!!*************!!!!!!!!!!!!*************!!!!!!!!!!!!*************
*
* @author Philip Warner
*/
public class BookEvents {
/**
* Base class for all book-related events. Has a book ID and is capable of
* displaying basic book data in a View.
*
* @author Philip Warner
*/
public static class BookEvent extends Event {
private static final long serialVersionUID = 74746691665235897L;
protected long m_bookId;
/**
* Constructor
*
* @param bookId ID of related book.
* @param description Description of this event.
*/
public BookEvent(long bookId, String description) {
super(description);
m_bookId = bookId;
}
/**
* Constructor
*
* @param bookId ID of related book.
* @param description Description of this event.
* @param e Exception related to this event.
*/
public BookEvent(long bookId, String description, Exception e) {
super(description, e);
m_bookId = bookId;
}
/**
* Get the related Book ID.
*
* @return ID of related book.
*/
public long getBookId() {
return m_bookId;
}
/**
* Class to implement the 'holder' model for view we create.
*
* @author Philip Warner
*/
protected class BookEventHolder {
long rowId;
BookEvent event;
TextView title;
TextView author;
TextView error;
TextView date;
Button retry;
CheckBox checkbox;
}
/**
* Return a view capable of displaying basic book event details, ideally usable by all BookEvent subclasses.
* This method also prepares the BookEventHolder object for the View.
*/
@Override
public View newListItemView(LayoutInflater inflater, Context context, BindableItemSQLiteCursor cursor, ViewGroup parent) {
View view = inflater.inflate(R.layout.book_event_info, parent, false);
ViewTagger.setTag(view, R.id.TAG_EVENT, this);
BookEventHolder holder = new BookEventHolder();
holder.event = this;
holder.rowId = cursor.getId();
holder.author = (TextView)view.findViewById(com.eleybourn.bookcatalogue.R.id.author);
holder.checkbox = (CheckBox)view.findViewById(com.eleybourn.bookcatalogue.R.id.checked);
holder.date = (TextView)view.findViewById(com.eleybourn.bookcatalogue.R.id.date);
holder.error = (TextView)view.findViewById(com.eleybourn.bookcatalogue.R.id.error);
holder.retry = (Button)view.findViewById(com.eleybourn.bookcatalogue.R.id.retry);
holder.title = ((TextView)view.findViewById(com.eleybourn.bookcatalogue.R.id.title));
ViewTagger.setTag(view, R.id.TAG_BOOK_EVENT_HOLDER, holder);
ViewTagger.setTag(holder.checkbox, R.id.TAG_BOOK_EVENT_HOLDER, holder);
ViewTagger.setTag(holder.retry, R.id.TAG_BOOK_EVENT_HOLDER, holder);
return view;
}
/**
* Display the related book details in the passed View object.
*/
@Override
public boolean bindView(View view, Context context, BindableItemSQLiteCursor bindableCursor, Object appInfo) {
final EventsCursor cursor = (EventsCursor)bindableCursor;
BookEventHolder holder = (BookEventHolder)ViewTagger.getTag(view, R.id.TAG_BOOK_EVENT_HOLDER);
CatalogueDBAdapter db = (CatalogueDBAdapter)appInfo;
// Update event info binding; the Views in the holder are unchanged, but when it is reused
// the Event and ID will change.
holder.event = this;
holder.rowId = cursor.getId();
ArrayList<Author> authors = db.getBookAuthorList(m_bookId);
String author;
if (authors.size() > 0) {
author = authors.get(0).getDisplayName();
if (authors.size() > 1)
author = author + " et. al.";
} else {
author = context.getString(R.string.unknown_uc);
}
String title;
try {
title = db.getBookTitle(m_bookId);
} catch (SQLiteDoneException e) {
title = context.getString(R.string.this_book_deleted_uc);
}
holder.title.setText(title);
holder.author.setText(Utils.format(context, R.string.by, author));
Exception e = this.getException();
if (e == null) {
holder.error.setText(this.getDescription());
} else {
holder.error.setText(e.getMessage());
}
String date = "(" + Utils.format(context, R.string.occurred_at, cursor.getEventDate().toLocaleString()) + ")";
holder.date.setText(date);
holder.retry.setVisibility(View.GONE);
holder.checkbox.setChecked(cursor.getIsSelected());
holder.checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
BookEventHolder holder = (BookEventHolder)ViewTagger.getTag(buttonView, R.id.TAG_BOOK_EVENT_HOLDER);
cursor.setIsSelected(holder.rowId,isChecked);
}});
return true;
}
/**
* Add ContextDialogItems relevant for the specific book the selected View is associated with.
* Subclass can override this and add items at end/start or just replace these completely.
*/
@Override
public void addContextMenuItems(final Context ctx, AdapterView<?> parent, final View v, final int position, final long id, ArrayList<ContextDialogItem> items, final Object appInfo) {
// EDIT BOOK
items.add(new ContextDialogItem(ctx.getString(R.string.edit_book), new Runnable() {
@Override
public void run() {
try {
GrSendBookEvent event = (GrSendBookEvent) ViewTagger.getTag(v, R.id.TAG_EVENT);
editBook(ctx, event.getBookId());
} catch (Exception e) {
// not a book event?
}
}}));
// TODO Reinstate goodreads search when goodreads work.editions API is available
//// SEARCH GOODREADS
//items.add(new ContextDialogItem(ctx.getString(R.string.visit_goodreads), new Runnable() {
// @Override
// public void run() {
// BookEventHolder holder = (BookEventHolder)ViewTagger.getTag(v, R.id.TAG_BOOK_EVENT_HOLDER);
// Intent i = new Intent(ctx, GoodreadsSearchCriteria.class);
// i.putExtra(GoodreadsSearchCriteria.EXTRA_BOOK_ID, holder.event.getBookId());
// ctx.startActivity(i);
// }}));
// DELETE EVENT
items.add(new ContextDialogItem(ctx.getString(R.string.delete_event), new Runnable() {
@Override
public void run() {
BookCatalogueApp.getQueueManager().deleteEvent(id);
}}));
}
};
/**
* Subclass of BookEvent that is the base class for all Event objects resulting from
* sending books to goodreads.
*
* @author Philip Warner
*/
public static class GrSendBookEvent extends BookEvent {
private static final long serialVersionUID = 1L;
public GrSendBookEvent(long bookId, String message) {
super(bookId, message);
}
public GrSendBookEvent(long bookId, String message, Exception e) {
super(bookId, message, e);
}
/**
* Resubmit this book and delete this event.
*/
public void retry() {
QueueManager qm = BookCatalogueApp.getQueueManager();
SendOneBookTask task = new SendOneBookTask(m_bookId);
// TODO: MAKE IT USE THE SAME QUEUE? Why????
qm.enqueueTask(task, BcQueueManager.QUEUE_SMALL_JOBS, 0);
qm.deleteEvent(this.getId());
}
/**
* Override to allow modification of view.
*/
@Override
public boolean bindView(View view, Context context, final BindableItemSQLiteCursor cursor, Object appInfo) {
// Get the 'standard' view.
super.bindView(view, context, cursor, appInfo);
// get book details
final BookEventHolder holder = (BookEventHolder)ViewTagger.getTag(view, R.id.TAG_BOOK_EVENT_HOLDER);
final CatalogueDBAdapter db = (CatalogueDBAdapter)appInfo;
final BooksCursor booksCursor = db.getBookForGoodreadsCursor(m_bookId);
final BooksRowView book = booksCursor.getRowView();
try {
// Hide parts of view based on current book details.
if (booksCursor.moveToFirst()) {
if (book.getIsbn().equals("")) {
holder.retry.setVisibility(View.GONE);
} else {
holder.retry.setVisibility(View.VISIBLE);
ViewTagger.setTag(holder.retry, this);
holder.retry.setOnClickListener(m_retryButtonListener);
}
} else {
holder.retry.setVisibility(View.GONE);
}
} finally {
// Always close
if (booksCursor != null)
booksCursor.close();
}
return true;
}
/**
* Override to allow a new context menu item.
*/
@Override
public void addContextMenuItems(final Context ctx, AdapterView<?> parent, final View v, final int position, final long id, ArrayList<ContextDialogItem> items, Object appInfo) {
super.addContextMenuItems(ctx, parent, v, position, id, items, appInfo);
final CatalogueDBAdapter db = (CatalogueDBAdapter)appInfo;
final BooksCursor booksCursor = db.getBookForGoodreadsCursor(m_bookId);
try {
final BooksRowView book = booksCursor.getRowView();
if (booksCursor.moveToFirst()) {
if (! (book.getIsbn().equals(""))) {
items.add(new ContextDialogItem(ctx.getString(R.string.retry_task), new Runnable() {
@Override
public void run() {
try {
GrSendBookEvent event = (GrSendBookEvent) ViewTagger.getTag(v, R.id.TAG_EVENT);
event.retry();
QueueManager.getQueueManager().deleteEvent(id);
} catch (Exception e) {
// not a book event?
}
}}));
}
}
} finally {
booksCursor.close();
}
}
};
/**
* Method to retry sending a book to goodreads.
*/
private static OnClickListener m_retryButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
BookEvent.BookEventHolder holder = (BookEvent.BookEventHolder)ViewTagger.getTag(v, R.id.TAG_BOOK_EVENT_HOLDER);
((GrSendBookEvent)holder.event).retry();
}
};
/**
* Method to edit a book details.
*/
private static void editBook(Context ctx, long bookId) {
Intent i = new Intent(ctx, BookEdit.class);
i.putExtra(CatalogueDBAdapter.KEY_ROWID, bookId);
i.putExtra(BookEdit.TAB, BookEdit.TAB_EDIT);
ctx.startActivity(i);
}
/*****************************************************************************************************
*
* 'General' purpose exception class
*
* @author Philip Warner
*/
public static class GrGeneralBookEvent extends GrSendBookEvent {
private static final long serialVersionUID = -7684121345325648066L;
public GrGeneralBookEvent(long bookId, Exception e, String message) {
super(bookId, message, e);
}
};
/**
* Exception indicating the book's ISBN could not be found at GoodReads
*
* @author Philip Warner
*
*/
public static class GrNoMatchEvent extends GrSendBookEvent implements HintOwner {
private static final long serialVersionUID = -7684121345325648066L;
public GrNoMatchEvent(long bookId) {
super(bookId, BookCatalogueApp.getResourceString(R.string.no_matching_book_found));
}
@Override
public int getHint() {
return R.string.explain_goodreads_no_match;
}
};
/**
* Exception indicating the book's ISBN was blank
*
* @author Philip Warner
*
*/
public static class GrNoIsbnEvent extends GrSendBookEvent implements HintOwner {
private static final long serialVersionUID = 7260496259505914311L;
public GrNoIsbnEvent(long bookId) {
super(bookId, BookCatalogueApp.getResourceString(R.string.no_isbn_stored_for_book));
}
@Override
public int getHint() {
return R.string.explain_goodreads_no_isbn;
}
}
}