package org.sugr.gearshift.ui;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.Html;
import android.text.Spanned;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.sugr.gearshift.G;
import org.sugr.gearshift.GearShiftApplication;
import org.sugr.gearshift.R;
import org.sugr.gearshift.core.TransmissionProfile;
import org.sugr.gearshift.core.TransmissionSession;
import org.sugr.gearshift.datasource.DataSource;
import org.sugr.gearshift.service.DataService;
import org.sugr.gearshift.service.DataServiceManager;
import org.sugr.gearshift.service.DataServiceManagerInterface;
import org.sugr.gearshift.ui.loader.TransmissionProfileSupportLoader;
import org.sugr.gearshift.ui.util.LocationDialogHelper;
import org.sugr.gearshift.ui.util.LocationDialogHelperInterface;
import org.sugr.gearshift.ui.util.QueueManagementDialogHelper;
import org.sugr.gearshift.ui.util.QueueManagementDialogHelperInterface;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public abstract class BaseTorrentActivity extends ColorizedToolbarActivity
implements TransmissionSessionInterface, DataServiceManagerInterface,
LocationDialogHelperInterface, QueueManagementDialogHelperInterface,
TransmissionProfileInterface, TorrentDetailFragment.PagerCallbacks {
protected TransmissionProfile profile;
protected TransmissionSession session;
protected DataServiceManager manager;
protected boolean refreshing = false;
protected String refreshType;
protected Menu menu;
protected BroadcastReceiver serviceReceiver;
protected LocationDialogHelper locationDialogHelper;
protected QueueManagementDialogHelper queueManagementDialogHelper;
protected boolean hasFatalError = false;
protected long lastServerActivity;
protected List<TransmissionProfile> profiles = new ArrayList<>();
private static final String STATE_LAST_SERVER_ACTIVITY = "last_server_activity";
private static final String STATE_FATAL_ERROR = "fatal_error";
private static final String STATE_ERROR_TYPE = "error_type";
private static final String STATE_ERROR_CODE = "error_Code";
private static final String STATE_ERROR_STRING = "error_string";
private static final String STATE_REFRESHING = "refreshing";
private int errorType;
private int errorCode;
private String errorString;
private LoaderManager.LoaderCallbacks<TransmissionProfile[]> profileLoaderCallbacks = new LoaderManager.LoaderCallbacks<TransmissionProfile[]>() {
@Override
public android.support.v4.content.Loader<TransmissionProfile[]> onCreateLoader(
int id, Bundle args) {
return new TransmissionProfileSupportLoader(BaseTorrentActivity.this);
}
@Override
public void onLoadFinished(
android.support.v4.content.Loader<TransmissionProfile[]> loader,
TransmissionProfile[] profiles) {
setProfiles(Arrays.asList(profiles));
}
@Override public void onLoaderReset(
android.support.v4.content.Loader<TransmissionProfile[]> loader) { }
};
/* The callback will get garbage collected if its a mere anon class */
private SharedPreferences.OnSharedPreferenceChangeListener profileChangeListener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (profile == null) return;
if (!key.endsWith(profile.getId())) return;
profile.load();
TransmissionProfile.setCurrentProfile(profile,
PreferenceManager.getDefaultSharedPreferences(BaseTorrentActivity.this));
setProfile(profile);
}
};
@Override protected void onCreate(Bundle savedInstanceState) {
locationDialogHelper = new LocationDialogHelper(this);
queueManagementDialogHelper = new QueueManagementDialogHelper(this);
serviceReceiver = new ServiceReceiver();
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(STATE_LAST_SERVER_ACTIVITY)) {
lastServerActivity = savedInstanceState.getLong(STATE_LAST_SERVER_ACTIVITY, 0);
}
if (savedInstanceState.containsKey(STATE_FATAL_ERROR)) {
hasFatalError = savedInstanceState.getBoolean(STATE_FATAL_ERROR, false);
if (hasFatalError) {
errorType = savedInstanceState.getInt(STATE_ERROR_TYPE, 0);
errorCode = savedInstanceState.getInt(STATE_ERROR_CODE, 0);
errorString = savedInstanceState.getString(STATE_ERROR_STRING);
}
}
if (savedInstanceState.containsKey(STATE_REFRESHING)) {
setRefreshing(true, null);
}
}
getSupportLoaderManager().initLoader(G.PROFILES_LOADER_ID, null, profileLoaderCallbacks);
super.onCreate(savedInstanceState);
}
@Override protected void onResume() {
super.onResume();
if (profile != null) {
colorize(profile);
if (manager == null) {
manager = new DataServiceManager(this, profile).startUpdating();
}
}
LocalBroadcastManager.getInstance(this).registerReceiver(
serviceReceiver, new IntentFilter(G.INTENT_SERVICE_ACTION_COMPLETE));
GearShiftApplication.setActivityVisible(true);
long now = new Date().getTime();
if (manager != null && now - lastServerActivity >= 60000) {
setRefreshing(true, DataService.Requests.GET_SESSION);
manager.getSession();
}
if (hasFatalError) {
showErrorMessage(errorType, errorCode, errorString);
}
}
@Override protected void onPause() {
super.onPause();
if (manager != null) {
manager.reset();
manager = null;
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(serviceReceiver);
GearShiftApplication.setActivityVisible(false);
locationDialogHelper.reset();
queueManagementDialogHelper.reset();
}
@Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_FATAL_ERROR, hasFatalError);
if (hasFatalError) {
outState.putInt(STATE_ERROR_TYPE, errorType);
outState.putInt(STATE_ERROR_CODE, errorCode);
outState.putString(STATE_ERROR_STRING, errorString);
}
outState.putLong(STATE_LAST_SERVER_ACTIVITY, lastServerActivity);
outState.putBoolean(STATE_REFRESHING, refreshing);
if (manager != null) {
manager.onSaveInstanceState(outState);
}
}
@Override public TransmissionSession getSession() {
return session;
}
@Override public void setSession(TransmissionSession session) {
this.session = session;
}
@Override public void setRefreshing(boolean refreshing, String type) {
if (!refreshing && refreshType != null && !refreshType.equals(type)) {
return;
}
this.refreshing = refreshing;
refreshType = refreshing ? type : null;
if (menu == null) {
return;
}
MenuItem item = menu.findItem(R.id.menu_refresh);
SwipeRefreshLayout swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
/* The swipe indicator is underneath the error layer */
if (refreshing && hasFatalError) {
swipeRefresh = null;
}
if (this.refreshing) {
if (swipeRefresh != null) {
swipeRefresh.setRefreshing(true);
swipeRefresh.setEnabled(false);
item.setEnabled(false);
} else {
View actionView = getLayoutInflater().inflate(R.layout.action_progress_bar, null);
MenuItemCompat.setActionView(item, actionView);
}
} else {
if (swipeRefresh != null) {
swipeRefresh.setRefreshing(false);
swipeRefresh.setEnabled(true);
}
item.setEnabled(true);
MenuItemCompat.setActionView(item, null);
}
}
@Override public DataServiceManager getDataServiceManager() {
return manager;
}
@Override public LocationDialogHelper getLocationDialogHelper() {
return locationDialogHelper;
}
@Override public QueueManagementDialogHelper getQueueManagementDialogHelper() {
return queueManagementDialogHelper;
}
@Override public TransmissionProfile getProfile() {
return profile;
}
@Override public void setProfile(TransmissionProfile profile) {
boolean newProfile = this.profile == null;
if (this.profile != null && this.profile.equals(profile) || this.profile == profile) {
return;
}
this.profile = profile;
if (manager != null) {
manager.reset();
}
if (menu != null) {
MenuItem item = menu.findItem(R.id.menu_refresh);
item.setVisible(profile != null);
if (findViewById(R.id.swipe_container) != null) {
findViewById(R.id.swipe_container).setEnabled(false);
}
}
if (profile == null) {
manager = null;
} else {
manager = new DataServiceManager(this, profile).startUpdating();
new SessionTask(this, SessionTask.Flags.START_TORRENT_TASK).execute();
colorize(profile);
}
SharedPreferences prefs = getSharedPreferences(
TransmissionProfile.getPreferencesName(),
Activity.MODE_PRIVATE);
if (prefs != null && newProfile) {
prefs.registerOnSharedPreferenceChangeListener(profileChangeListener);
}
}
@Override public List<TransmissionProfile> getProfiles() {
return new ArrayList<>(profiles);
}
protected void showErrorMessage(int error, int code, String string) {
findViewById(R.id.fatal_error_layer).setVisibility(View.VISIBLE);
TextView text = (TextView) findViewById(R.id.transmission_error);
text.setText(getErrorMessage(error, code, string));
setSession(null);
}
protected void hideErrorMessage() {
findViewById(R.id.fatal_error_layer).setVisibility(View.GONE);
}
protected abstract void onSessionTaskPostExecute(TransmissionSession session);
protected abstract void onTorrentTaskPostExecute(Cursor cursor, boolean added,
boolean removed, boolean statusChanged,
boolean incompleteMetadata, boolean connected);
protected abstract boolean handleSuccessServiceBroadcast(String type, Intent intent);
protected abstract boolean handleErrorServiceBroadcast(String type, int error, Intent intent);
protected void setProfiles(List<TransmissionProfile> profiles) {
this.profiles.clear();
if (profiles.isEmpty()) {
TransmissionProfile.setCurrentProfile(null,
PreferenceManager.getDefaultSharedPreferences(this));
setProfile(null);
return;
}
this.profiles.addAll(profiles);
String currentId = TransmissionProfile.getCurrentProfileId(
PreferenceManager.getDefaultSharedPreferences(this));
boolean isProfileSet = false;
for (TransmissionProfile prof : profiles) {
if (prof.getId().equals(currentId)) {
setProfile(prof);
isProfileSet = true;
break;
}
}
if (!isProfileSet) {
if (this.profiles.size() > 0) {
setProfile(profiles.get(0));
} else {
setProfile(null);
}
TransmissionProfile.setCurrentProfile(getProfile(),
PreferenceManager.getDefaultSharedPreferences(this));
}
}
private Spanned getErrorMessage(int error, int code, String string) {
if (error == DataService.Errors.NO_CONNECTIVITY) {
return Html.fromHtml(getString(R.string.no_connectivity_empty_list));
} else if (error == DataService.Errors.ACCESS_DENIED) {
return Html.fromHtml(getString(R.string.access_denied_empty_list));
} else if (error == DataService.Errors.NO_JSON) {
return Html.fromHtml(getString(R.string.no_json_empty_list));
} else if (error == DataService.Errors.NO_CONNECTION) {
return Html.fromHtml(getString(R.string.no_connection_empty_list));
} else if (error == DataService.Errors.THREAD_ERROR) {
return Html.fromHtml(getString(R.string.thread_error_empty_list));
} else if (error == DataService.Errors.RESPONSE_ERROR) {
return Html.fromHtml(getString(R.string.response_error_empty_list));
} else if (error == DataService.Errors.TIMEOUT) {
return Html.fromHtml(getString(R.string.timeout_empty_list));
} else if (error == DataService.Errors.OUT_OF_MEMORY) {
return Html.fromHtml(getString(R.string.out_of_memory_empty_list));
} else if (error == DataService.Errors.JSON_PARSE_ERROR) {
return Html.fromHtml(getString(R.string.json_parse_empty_list));
} else if (error == DataService.Errors.GENERIC_HTTP) {
return Html.fromHtml(String.format(getString(R.string.generic_http_empty_list),
code, string));
}
return null;
}
protected class SessionTask extends AsyncTask<Void, Void, TransmissionSession> {
DataSource readSource;
boolean startTorrentTask;
public class Flags {
public static final int START_TORRENT_TASK = 1;
}
public SessionTask(Context context, int flags) {
super();
readSource = new DataSource(context);
if ((flags & Flags.START_TORRENT_TASK) == Flags.START_TORRENT_TASK) {
startTorrentTask = true;
}
}
@Override protected TransmissionSession doInBackground(Void... ignored) {
try {
if (profile == null) {
return null;
}
readSource.open();
TransmissionSession session = readSource.getSession(profile.getId());
session.setDownloadDirectories(profile,
readSource.getDownloadDirectories(profile.getId()));
return session;
} finally {
if (readSource.isOpen()) {
readSource.close();
}
}
}
@Override protected void onPostExecute(TransmissionSession session) {
if (session == null) {
return;
}
setSession(session);
if (session.getRPCVersion() >= TransmissionSession.FREE_SPACE_METHOD_RPC_VERSION
&& manager != null) {
manager.getFreeSpace(session.getDownloadDir());
}
if (startTorrentTask) {
new TorrentTask(BaseTorrentActivity.this, 0).execute();
}
onSessionTaskPostExecute(session);
}
}
protected class TorrentTask extends AsyncTask<Void, Void, Cursor> {
DataSource readSource;
boolean added, removed, statusChanged, incompleteMetadata, update, connected;
public class Flags {
public static final int HAS_ADDED = 1;
public static final int HAS_REMOVED = 1 << 1;
public static final int HAS_STATUS_CHANGED = 1 << 2;
public static final int HAS_INCOMPLETE_METADATA = 1 << 3;
public static final int UPDATE = 1 << 4;
public static final int CONNECTED = 1 << 5;
}
public TorrentTask(Context context, int flags) {
super();
readSource = new DataSource(context);
if ((flags & Flags.HAS_ADDED) == Flags.HAS_ADDED) {
added = true;
}
if ((flags & Flags.HAS_REMOVED) == Flags.HAS_REMOVED) {
removed = true;
}
if ((flags & Flags.HAS_STATUS_CHANGED) == Flags.HAS_STATUS_CHANGED) {
statusChanged = true;
}
if ((flags & Flags.HAS_INCOMPLETE_METADATA) == Flags.HAS_INCOMPLETE_METADATA) {
incompleteMetadata = true;
}
if ((flags & Flags.UPDATE) == Flags.UPDATE) {
update = true;
}
if ((flags & Flags.CONNECTED) == Flags.CONNECTED) {
connected = true;
}
}
@Override protected Cursor doInBackground(Void... unused) {
try {
if (profile == null) {
return null;
}
readSource.open();
return readSource.getTorrentCursor(profile.getId(),
PreferenceManager.getDefaultSharedPreferences(readSource.getContext()));
} finally {
if (readSource.isOpen()) {
readSource.close();
}
}
}
@Override protected void onPostExecute(Cursor cursor) {
if (connected) {
setRefreshing(false, DataService.Requests.GET_TORRENTS);
}
if (cursor == null) {
return;
}
if (update && manager != null) {
update = false;
manager.update();
}
onTorrentTaskPostExecute(cursor, added, removed, statusChanged,
incompleteMetadata, connected);
}
}
private class ServiceReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
int error = intent.getIntExtra(G.ARG_ERROR, 0);
String profileId = intent.getStringExtra(G.ARG_PROFILE_ID);
String type = intent.getStringExtra(G.ARG_REQUEST_TYPE);
if (profile == null || profileId == null) {
return;
}
hasFatalError = false;
switch (type) {
case DataService.Requests.GET_SESSION:
case DataService.Requests.SET_SESSION:
case DataService.Requests.GET_TORRENTS:
case DataService.Requests.ADD_TORRENT:
case DataService.Requests.REMOVE_TORRENT:
case DataService.Requests.SET_TORRENT:
case DataService.Requests.SET_TORRENT_ACTION:
case DataService.Requests.SET_TORRENT_LOCATION:
case DataService.Requests.GET_FREE_SPACE:
if (!type.equals(DataService.Requests.GET_TORRENTS)) {
setRefreshing(false, type);
}
lastServerActivity = new Date().getTime();
if (!profileId.equals(profile.getId())) {
/* Only torrent additions are handled out-of-profile, all errors are ignored */
if (type.equals(DataService.Requests.ADD_TORRENT)) {
if (error == 0) {
Toast.makeText(BaseTorrentActivity.this,
R.string.torrent_added_different_profile, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(BaseTorrentActivity.this,
getErrorMessage(error, intent.getIntExtra(G.ARG_ERROR_CODE, 0),
intent.getStringExtra(G.ARG_ERROR_STRING)),
Toast.LENGTH_LONG).show();
}
}
return;
}
if (error == 0) {
if (!handleSuccessServiceBroadcast(type, intent)) {
return;
}
hideErrorMessage();
} else {
if (!handleErrorServiceBroadcast(type, error, intent)) {
return;
}
if (error == DataService.Errors.DUPLICATE_TORRENT) {
Toast.makeText(BaseTorrentActivity.this,
R.string.duplicate_torrent, Toast.LENGTH_SHORT).show();
} else if (error == DataService.Errors.INVALID_TORRENT) {
Toast.makeText(BaseTorrentActivity.this,
R.string.invalid_torrent, Toast.LENGTH_SHORT).show();
} else {
hasFatalError = true;
errorType = error;
errorCode = intent.getIntExtra(G.ARG_ERROR_CODE, 0);
errorString = intent.getStringExtra(G.ARG_ERROR_STRING);
setRefreshing(false, refreshType);
showErrorMessage(errorType, errorCode, errorString);
}
}
break;
}
}
}
}