package org.ebookdroid.ui.opds.adapters;
import org.sufficientlysecure.viewer.R;
import org.ebookdroid.common.cache.CacheManager;
import org.ebookdroid.common.cache.ThumbnailFile;
import org.ebookdroid.common.settings.OpdsSettings;
import org.ebookdroid.opds.ExtentedEntryBuilder;
import org.ebookdroid.opds.OPDSClient;
import org.ebookdroid.opds.exceptions.AuthorizationRequiredException;
import org.ebookdroid.opds.exceptions.OPDSException;
import org.ebookdroid.opds.model.Book;
import org.ebookdroid.opds.model.BookDownloadLink;
import org.ebookdroid.opds.model.Entry;
import org.ebookdroid.opds.model.Feed;
import org.ebookdroid.opds.model.Link;
import android.annotation.TargetApi;
import android.content.Context;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.BaseExpandableListAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import org.emdev.ui.actions.ActionController;
import org.emdev.ui.actions.ActionDialogBuilder;
import org.emdev.ui.actions.ActionEx;
import org.emdev.ui.actions.ActionMethod;
import org.emdev.ui.actions.IActionController;
import org.emdev.ui.actions.params.Constant;
import org.emdev.ui.actions.params.EditableValue;
import org.emdev.ui.actions.params.EditableValue.PasswordEditable;
import org.emdev.ui.adapters.BaseViewHolder;
import org.emdev.ui.widget.TextViewMultilineEllipse;
import org.emdev.utils.LengthUtils;
import org.emdev.utils.listeners.ListenerProxy;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@TargetApi(8)
public class OPDSAdapter extends BaseExpandableListAdapter {
final Context context;
final IActionController<?> actions;
final OPDSClient client;
final List<Feed> rootFeeds;
volatile Feed currentFeed;
final ListenerProxy listeners = new ListenerProxy(FeedListener.class);
final OPDSTaskExecutor executor = new OPDSTaskExecutor(this);
public OPDSAdapter(final Context context, final IActionController<?> actions) {
this.context = context;
this.actions = new ActionController<OPDSAdapter>(actions, this);
this.client = new OPDSClient(new ExtentedEntryBuilder());
this.rootFeeds = new ArrayList<Feed>();
final JSONArray feeds = OpdsSettings.current().opdsCatalogs;
for (int i = 0, n = feeds.length(); i < n; i++) {
try {
final JSONObject obj = feeds.getJSONObject(i);
final String alias = obj.getString("alias");
final String url = obj.getString("url");
final String login = obj.optString("login");
final String password = obj.optString("password");
if (LengthUtils.isAllNotEmpty(alias, url)) {
rootFeeds.add(new Feed(alias, url, login, password));
}
} catch (final JSONException ex) {
ex.printStackTrace();
}
}
// TODO remove in release
if (rootFeeds.isEmpty()) {
addFeeds(new Feed("Flibusta", "http://flibusta.net/opds"), new Feed("Plough",
"http://www.plough.com/ploughCatalog_opds.xml"));
}
this.currentFeed = null;
}
protected void store() {
final JSONArray catalogs = new JSONArray();
for (final Feed feed : rootFeeds) {
try {
final JSONObject newCatalog = new JSONObject();
newCatalog.put("alias", feed.title);
newCatalog.put("url", feed.link.uri);
if (feed.login != null) {
newCatalog.put("login", feed.login);
newCatalog.put("password", feed.password);
}
catalogs.put(newCatalog);
} catch (final JSONException ex) {
ex.printStackTrace();
}
}
OpdsSettings.changeOpdsCatalogs(catalogs);
}
@Override
protected void finalize() {
close();
}
public void close() {
client.close();
}
public void addFeed(final String alias, final String url) {
addFeeds(new Feed(alias, url));
}
public void addFeed(final String alias, final String url, final String login, final String password) {
addFeeds(new Feed(alias, url, login, password));
}
public void addFeeds(final Feed... feeds) {
for (final Feed feed : feeds) {
rootFeeds.add(feed);
}
store();
if (currentFeed == null) {
notifyDataSetChanged();
}
}
public void editFeed(final Feed feed, final String alias, final String url, final String login,
final String password) {
if (feed.id.equals(url)) {
feed.title = alias;
feed.login = login;
feed.password = password;
store();
if (currentFeed == null) {
notifyDataSetInvalidated();
}
return;
}
final Feed newFeed = new Feed(alias, url, login, password);
final int index = rootFeeds.indexOf(feed);
if (index == -1) {
rootFeeds.add(newFeed);
} else {
rootFeeds.set(index, newFeed);
}
store();
if (currentFeed == null) {
if (index == -1) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
public void removeFeed(final Feed feed) {
if (rootFeeds.remove(feed)) {
store();
notifyDataSetChanged();
}
}
public void setCurrentFeed(final Feed feed) {
if (feed != null && feed == this.currentFeed) {
feed.books.clear();
feed.children.clear();
feed.loadedAt = 0;
}
this.currentFeed = feed;
notifyDataSetInvalidated();
executor.startLoadFeed(feed);
}
public Feed getCurrentFeed() {
return currentFeed;
}
@Override
public int getGroupCount() {
if (currentFeed == null) {
return rootFeeds.size();
}
return currentFeed.children.size() + currentFeed.books.size();
}
@Override
@SuppressWarnings("unchecked")
public Entry getGroup(final int groupPosition) {
if (currentFeed == null) {
return rootFeeds.get(groupPosition);
}
return getItem(groupPosition, currentFeed.children, currentFeed.books);
}
@Override
public long getGroupId(final int groupPosition) {
return groupPosition;
}
@Override
public int getChildrenCount(final int groupPosition) {
final Entry group = getGroup(groupPosition);
if (group instanceof Feed) {
return ((Feed) group).facets.size();
} else if (group instanceof Book) {
final int size = ((Book) group).downloads.size();
return size > 1 ? size : 0;
}
return 0;
}
@Override
public Object getChild(final int groupPosition, final int childPosition) {
final Entry group = getGroup(groupPosition);
if (group instanceof Feed) {
return ((Feed) group).facets.get(childPosition);
} else if (group instanceof Book) {
return ((Book) group).downloads.get(childPosition);
}
return null;
}
@Override
public long getChildId(final int groupPosition, final int childPosition) {
return childPosition;
}
@Override
public boolean isChildSelectable(final int groupPosition, final int childPosition) {
return true;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(final int groupPosition, final boolean isExpanded, final View convertView,
final ViewGroup parent) {
return getItemView(getGroup(groupPosition), false, convertView, parent);
}
@Override
public View getChildView(final int groupPosition, final int childPosition, final boolean isLastChild,
final View convertView, final ViewGroup parent) {
return getItemView(getChild(groupPosition, childPosition), true, convertView, parent);
}
protected Entry getItem(final int i, final List<? extends Entry>... lists) {
int index = i;
for (final List<? extends Entry> l : lists) {
if (index < 0) {
return null;
}
final int size = l.size();
if (index < size) {
return l.get(index);
}
index -= size;
}
return null;
}
@SuppressWarnings("deprecation")
protected View getItemView(final Object item, final boolean child, final View view, final ViewGroup parent) {
final ViewHolder holder = BaseViewHolder.getOrCreateViewHolder(ViewHolder.class, R.layout.opdsitem, view,
parent);
if (item instanceof Entry) {
final Entry entry = (Entry) item;
holder.textView.setText(entry.title);
if (entry.content != null) {
String decoded = entry.content.content;
try {
decoded = URLDecoder.decode(entry.content.content);
} catch (Exception ex) {
}
holder.info.setText(Html.fromHtml(decoded));
} else {
holder.info.setText("");
}
if (entry instanceof Feed) {
holder.imageView.setImageResource(R.drawable.opds_item_feed);
} else if (entry instanceof Book) {
final ThumbnailFile thumbnailFile = CacheManager.getThumbnailFile(entry.id);
if (thumbnailFile.exists()) {
holder.imageView.setImageBitmap(thumbnailFile.getRawImage());
} else {
holder.imageView.setImageResource(R.drawable.opds_item_book);
}
}
} else if (item instanceof Link) {
final Link link = (Link) item;
holder.textView.setText(link.type);
holder.info.setText("");
holder.imageView.setImageResource(R.drawable.opds_item_book);
}
final MarginLayoutParams lp = (MarginLayoutParams) holder.imageView.getLayoutParams();
lp.leftMargin = child ? 50 : 4;
return holder.getView();
}
public void addListener(final FeedListener listener) {
listeners.addListener(listener);
}
public void removeListener(final FeedListener listener) {
listeners.removeListener(listener);
}
public void downloadBook(final Book book, final int linkIndex) {
if (book == null || linkIndex >= book.downloads.size()) {
return;
}
final BookDownloadLink link = book.downloads.get(linkIndex);
executor.startBookDownload(book, link);
}
public void showErrorDlg(final int pbLabel, final int pbAction, final Object result, final OPDSException error) {
final String msg = error.getErrorMessage();
final ActionDialogBuilder b = new ActionDialogBuilder(context, actions);
b.setTitle(R.string.opds_error_title);
b.setMessage(R.string.opds_error_msg, msg);
if (pbAction != R.id.actions_no_action) {
b.setPositiveButton(pbLabel, pbAction, new Constant("info", result));
b.setNegativeButton();
} else {
b.setPositiveButton(pbLabel, pbAction);
}
b.show();
}
public void showAuthDlg(final Object info) {
final ActionDialogBuilder b = new ActionDialogBuilder(context, actions);
final View view = LayoutInflater.from(context).inflate(R.layout.opds_auth_dlg, null);
final EditText editUsername = (EditText) view.findViewById(R.id.opds_auth_editUsername);
final EditText editPassword = (EditText) view.findViewById(R.id.opds_auth_editPassword);
b.setTitle(R.string.opds_authfeed_title);
b.setMessage(R.string.opds_authfeed_msg);
b.setView(view);
b.setPositiveButton(R.string.opds_authfeed_ok, R.id.actions_setFeedAuth, new EditableValue("username",
editUsername), new EditableValue("password", editPassword), new Constant("info", info));
b.setNegativeButton();
b.show();
}
@ActionMethod(ids = R.id.actions_setFeedAuth)
public void setFeedAuth(final ActionEx action) {
final String username = action.getParameter("username").toString();
final String password = ((PasswordEditable) action.getParameter("password")).getPassword();
final Object info = action.getParameter("info");
if (info instanceof FeedTaskResult) {
final AuthorizationRequiredException ex = (AuthorizationRequiredException) ((FeedTaskResult) info).error;
client.setAuthorization(ex.host, username, password);
actions.getOrCreateAction(R.id.opdsrefreshfolder).run();
} else if (info instanceof DownloadBookResult) {
final DownloadBookResult result = (DownloadBookResult) info;
final AuthorizationRequiredException ex = (AuthorizationRequiredException) result.error;
client.setAuthorization(ex.host, username, password);
executor.startBookDownload(result.book, result.link);
}
store();
}
@ActionMethod(ids = R.id.actions_retryDownloadBook)
public void retryDownload(final ActionEx action) {
final DownloadBookResult info = action.getParameter("info");
executor.startBookDownload(info.book, info.link);
}
public void startLoadThumbnails() {
executor.startLoadThumbnails(currentFeed);
}
public static class ViewHolder extends BaseViewHolder {
TextView textView;
ImageView imageView;
TextViewMultilineEllipse info;
@Override
public void init(final View convertView) {
super.init(convertView);
textView = (TextView) convertView.findViewById(R.id.opdsItemText);
imageView = (ImageView) convertView.findViewById(R.id.opdsItemIcon);
info = (TextViewMultilineEllipse) convertView.findViewById(R.id.opdsDescription);
}
}
}