/*
* Copyright (C) 2005-2009 Team XBMC
* http://xbmc.org
*
* This Program 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 2, or (at your option)
* any later version.
*
* This Program 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 XBMC Remote; see the file license. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
package org.xbmc.android.remote.business;
import java.util.ArrayList;
import java.util.List;
import org.xbmc.android.util.ClientFactory;
import org.xbmc.android.util.Crc32;
import org.xbmc.api.business.DataResponse;
import org.xbmc.api.business.INotifiableManager;
import org.xbmc.api.data.IControlClient;
import org.xbmc.api.data.IInfoClient;
import org.xbmc.api.data.IMusicClient;
import org.xbmc.api.data.ITvShowClient;
import org.xbmc.api.data.IVideoClient;
import org.xbmc.api.object.ICoverArt;
import org.xbmc.api.presentation.INotifiableController;
import org.xbmc.api.type.CacheType;
import org.xbmc.api.type.SortType;
import org.xbmc.api.type.ThumbSize;
import org.xbmc.httpapi.WifiStateException;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Handler;
import android.util.Log;
/**
* Super class of the wrappers, keeps common code.
*
* @author Team XBMC
*/
public abstract class AbstractManager implements INotifiableManager {
public static final Boolean DEBUG = false;
protected static final String TAG = "AbstractManager";
public static final String PREF_SORT_BY_PREFIX = "sort_by_";
public static final String PREF_SORT_ORDER_PREFIX = "sort_order_";
/* The idea of the sort keys is to remember different sort settings for
* each type. In your controller, make sure you run setSortKey() in the
* onCreate() method.
*/
public static final int PREF_SORT_KEY_ALBUM = 1;
public static final int PREF_SORT_KEY_ARTIST = 2;
public static final int PREF_SORT_KEY_SONG = 3;
public static final int PREF_SORT_KEY_GENRE = 4;
public static final int PREF_SORT_KEY_FILEMODE = 5;
public static final int PREF_SORT_KEY_SHOW = 6;
public static final int PREF_SORT_KEY_MOVIE = 7;
public static final int PREF_SORT_KEY_EPISODE = 8;
protected INotifiableController mController = null;
protected Handler mHandler;
protected SharedPreferences mPref;
protected int mCurrentSortKey;
protected List<Runnable> failedRequests = new ArrayList<Runnable>();
/**
* Sets the handler used in the looping thread
* @param handler
*/
public void setHandler(Handler handler) {
mHandler = handler;
}
public void setController(INotifiableController controller) {
mController = controller;
}
public void postActivity() {
AbstractThread.quitThreads();
}
/**
* Returns the InfoClient class
* @param response Response object
* @return
* @throws WifiStateException
*/
protected IInfoClient info(Context context) throws WifiStateException {
return ClientFactory.getInfoClient(this, context);
}
/**
* Returns the ControlClient class
* @param response Response object
* @return
* @throws WifiStateException
*/
protected IControlClient control(Context context) throws WifiStateException {
return ClientFactory.getControlClient(this, context);
}
/**
* Returns the VideoClient class
* @param response Response object
* @return
* @throws WifiStateException
*/
protected IVideoClient video(Context context) throws WifiStateException {
return ClientFactory.getVideoClient(this, context);
}
/**
* Returns the MusicClient class
* @param response Response object
* @return
* @throws WifiStateException
*/
protected IMusicClient music(Context context) throws WifiStateException {
return ClientFactory.getMusicClient(this, context);
}
protected ITvShowClient shows(Context context) throws WifiStateException {
return ClientFactory.getTvShowClient(this, context);
}
/**
* Calls the UI thread's callback code.
* @param response Response object
*/
public void onFinish(DataResponse<?> response) {
if (mController != null) {
//Log.i(TAG, "*** posting onFinish through controller");
mController.runOnUI(response);
}else{
Log.w(TAG, "*** ignoring onFinish, controller is null.");
//mHandler.post(response);
}
}
public Bitmap getCoverSync(final ICoverArt cover, final int thumbSize){
if(MemCacheThread.isInCache(cover, thumbSize))
return MemCacheThread.getCover(cover, thumbSize);
else if(DiskCacheThread.isInCache(cover, thumbSize))
return DiskCacheThread.getCover(cover, thumbSize);
else
return null;
}
public boolean coverLoaded(final ICoverArt cover, final int thumbSize){
return (MemCacheThread.isInCache(cover, thumbSize) || DiskCacheThread.isInCache(cover, thumbSize));
}
/**
* Returns bitmap of any cover. Note that the callback is done by the
* helper methods below.
* @param response Response object
*/
public void getCover(final DataResponse<Bitmap> response, final ICoverArt cover, final int thumbSize, final Bitmap defaultCover, final Context context, final boolean getFromCacheOnly) {
mHandler.post(new Runnable() {
public void run() {
if (cover.getCrc() != 0L) {
// first, try mem cache (only if size = small, other sizes aren't mem-cached.
if (thumbSize == ThumbSize.SMALL || thumbSize == ThumbSize.MEDIUM) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] Trying memory (" + Crc32.formatAsHexLowerCase(cover.getCrc()) + ")");
getCoverFromMem(response, cover, thumbSize, defaultCover, context, getFromCacheOnly);
} else {
if (getFromCacheOnly) {
Log.e(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] ERROR: NOT downloading big covers is a bad idea because they are not cached!");
response.value = null;
onFinish(response);
} else {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] Downloading directly");
getCoverFromNetwork(response, cover, thumbSize, context);
}
}
} else {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] no crc, skipping.");
response.value = null;
onFinish(response);
}
}
});
}
/**
* Synchronously downloads a cover and stores on on disk cache.
* @param cover Cover to cache
* @param manager Reference to manager
* @param context Reference to context
* @return True if cover has been downloaded, false otherwise.
*/
public static boolean cacheCover(final ICoverArt cover, final INotifiableManager manager, final Context context) {
if (!DiskCacheThread.isInCache(cover, ThumbSize.MEDIUM)) {
return DownloadThread.download(null, cover, ThumbSize.MEDIUM, null, manager, context, false);
}
return false;
}
/**
* Tries to get small cover from memory, then from disk, then download it from XBMC.
* @param response Response object
* @param cover Get cover for this object
*/
protected void getCoverFromMem(final DataResponse<Bitmap> response, final ICoverArt cover, final int thumbSize, Bitmap defaultCover, final Context context, final boolean getFromCacheOnly) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + "] Checking in mem cache..");
MemCacheThread.get().getCover(new DataResponse<Bitmap>() {
public void run() {
if (value == null) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] empty");
// then, try sdcard cache
getCoverFromDisk(response, cover, thumbSize, context, getFromCacheOnly);
} else {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] FOUND in memory!");
response.value = value;
response.cacheType = CacheType.MEMORY;
onFinish(response);
}
}
}, cover, thumbSize, mController, defaultCover);
}
/**
* Tries to get cover from disk, then download it from XBMC.
* @param response Response object
* @param cover Get cover for this object
* @param thumbSize Cover size
*/
protected void getCoverFromDisk(final DataResponse<Bitmap> response, final ICoverArt cover, final int thumbSize, final Context context, final boolean getFromCacheOnly) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + "] Checking in disk cache..");
DiskCacheThread.get().getCover(new DataResponse<Bitmap>() {
public void run() {
if (value == null) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] Disk cache empty.");
if (response.postCache()) {
// well, let's download
if (getFromCacheOnly) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] Skipping download.");
response.value = null;
onFinish(response);
} else {
getCoverFromNetwork(response, cover, thumbSize, context);
}
}
} else {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + ThumbSize.getDir(thumbSize) + "] FOUND on disk!");
response.value = value;
response.cacheType = CacheType.SDCARD;
onFinish(response);
}
}
}, cover, thumbSize, mController);
}
/**
* Last stop: try to download from XBMC.
* @param response Response object
* @param cover Get cover for this object
* @param thumbSize Cover size
*/
protected void getCoverFromNetwork(final DataResponse<Bitmap> response, final ICoverArt cover, final int thumbSize, final Context context) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + "] Downloading..");
DownloadThread.get().getCover(new DataResponse<Bitmap>() {
public void run() {
if (value == null) {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + "] Download empty");
} else {
if (DEBUG) Log.i(TAG, "[" + cover.getId() + "] DOWNLOADED (" + value.getWidth() + "x" + value.getHeight() + ")!");
response.cacheType = CacheType.NETWORK;
response.value = value;
}
onFinish(response); // callback in any case, since we don't go further than that.
}
}, cover, thumbSize, mController, this, context);
}
/**
* Commands failed because of wrong connection state are special. After the connection has the right state we
* could retry the command
*/
public void onWrongConnectionState(int state, Command<?> cmd) {
failedRequests.add(cmd);
if (mController != null)
mController.onWrongConnectionState(state, this, cmd);
}
public void onError(Exception e) {
if (mController != null) {
mController.onError(e);
}
}
public void onMessage(String message) {
if (mController != null) {
mController.onMessage(message);
}
}
public void onMessage(int code, String message) {
onMessage(message);
}
public void retryAll() {
Log.d(TAG, "Posting retries to the queue");
mHandler.post(new Runnable() {
public void run() {
Log.d(TAG, "runnable started, posting retries");
while(failedRequests.size() > 0) {
if(mHandler.post(failedRequests.get(0)))
Log.d(TAG, "Runnable posted");
else
Log.d(TAG, "Runnable coudln't be posted");
failedRequests.remove(0);
}
}
});
}
/**
* Sets the static reference to the preferences object. Used to obtain
* current sort values.
* @param pref
*/
public void setPreferences(SharedPreferences pref) {
mPref = pref;
}
/**
* Sets which kind of view is currently active.
* @param sortKey
*/
public void setSortKey(int sortKey) {
mCurrentSortKey = sortKey;
}
public void post(Runnable runnable)
{
mHandler.post(runnable);
}
/**
* Returns currently saved "sort by" value. If the preference was not set yet, or
* if the current sort key is not set, return default value.
* @param type Default value
* @return Sort by field
*/
protected int getSortBy(int type) {
if (mPref != null) {
return mPref.getInt(PREF_SORT_BY_PREFIX + mCurrentSortKey, type);
}
return type;
}
/**
* Returns currently saved "sort by" value. If the preference was not set yet, or
* if the current sort key is not set, return "ASC".
* @return Sort order
*/
protected String getSortOrder() {
if (mPref != null) {
return mPref.getString(PREF_SORT_ORDER_PREFIX + mCurrentSortKey, SortType.ORDER_ASC);
}
return SortType.ORDER_ASC;
}
protected boolean getHideWatched(Context context) {
return context.getSharedPreferences("global", Context.MODE_PRIVATE).getBoolean("HideWatched", false);
}
}