You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1375 lines
60 KiB
1375 lines
60 KiB
/* |
|
* Copyright 2010-2024 Eric Kok et al. |
|
* |
|
* Transdroid 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. |
|
* |
|
* Transdroid 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 Transdroid. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
package org.transdroid.core.gui; |
|
|
|
import android.annotation.TargetApi; |
|
import android.app.SearchManager; |
|
import android.content.ContentResolver; |
|
import android.content.Intent; |
|
import android.net.Uri; |
|
import android.os.AsyncTask; |
|
import android.os.Build; |
|
import android.os.Bundle; |
|
import android.view.Menu; |
|
import android.view.MenuItem; |
|
import android.view.View; |
|
import android.view.ViewGroup; |
|
import android.widget.AdapterView; |
|
import android.widget.AdapterView.OnItemClickListener; |
|
import android.widget.ListView; |
|
import androidx.annotation.NonNull; |
|
import androidx.appcompat.app.ActionBarDrawerToggle; |
|
import androidx.appcompat.app.AppCompatActivity; |
|
import androidx.appcompat.widget.ActionMenuView; |
|
import androidx.appcompat.widget.SearchView; |
|
import androidx.appcompat.widget.Toolbar; |
|
import androidx.drawerlayout.widget.DrawerLayout; |
|
import com.getbase.floatingactionbutton.FloatingActionButton; |
|
import com.getbase.floatingactionbutton.FloatingActionsMenu; |
|
import com.nispok.snackbar.Snackbar; |
|
import com.nispok.snackbar.SnackbarManager; |
|
import com.nispok.snackbar.enums.SnackbarType; |
|
import org.androidannotations.annotations.AfterViews; |
|
import org.androidannotations.annotations.Background; |
|
import org.androidannotations.annotations.Bean; |
|
import org.androidannotations.annotations.Click; |
|
import org.androidannotations.annotations.EActivity; |
|
import org.androidannotations.annotations.FragmentById; |
|
import org.androidannotations.annotations.InstanceState; |
|
import org.androidannotations.annotations.OnActivityResult; |
|
import org.androidannotations.annotations.OptionsItem; |
|
import org.androidannotations.annotations.SystemService; |
|
import org.androidannotations.annotations.UiThread; |
|
import org.androidannotations.annotations.ViewById; |
|
import org.apache.http.HttpResponse; |
|
import org.apache.http.HttpStatus; |
|
import org.apache.http.client.methods.HttpGet; |
|
import org.apache.http.impl.client.DefaultHttpClient; |
|
import org.apache.http.impl.cookie.BasicClientCookie; |
|
import org.transdroid.R; |
|
import org.transdroid.core.app.search.SearchHelper_; |
|
import org.transdroid.core.app.settings.ApplicationSettings; |
|
import org.transdroid.core.app.settings.ServerSetting; |
|
import org.transdroid.core.app.settings.SettingsUtils; |
|
import org.transdroid.core.app.settings.SystemSettings; |
|
import org.transdroid.core.app.settings.WebsearchSetting; |
|
import org.transdroid.core.gui.lists.LocalTorrent; |
|
import org.transdroid.core.gui.lists.SimpleListItem; |
|
import org.transdroid.core.gui.log.Log; |
|
import org.transdroid.core.gui.log.LogUncaughtExceptionHandler; |
|
import org.transdroid.core.gui.navigation.FilterListAdapter; |
|
import org.transdroid.core.gui.navigation.FilterListAdapter_; |
|
import org.transdroid.core.gui.navigation.Label; |
|
import org.transdroid.core.gui.navigation.NavigationFilter; |
|
import org.transdroid.core.gui.navigation.NavigationHelper; |
|
import org.transdroid.core.gui.navigation.RefreshableActivity; |
|
import org.transdroid.core.gui.navigation.StatusType; |
|
import org.transdroid.core.gui.rss.RssFeedsActivity_; |
|
import org.transdroid.core.gui.search.FilePickerHelper; |
|
import org.transdroid.core.gui.search.UrlEntryDialog; |
|
import org.transdroid.core.gui.settings.MainSettingsActivity_; |
|
import org.transdroid.core.service.AppUpdateJob; |
|
import org.transdroid.core.service.ConnectivityHelper; |
|
import org.transdroid.core.service.RssCheckerJob; |
|
import org.transdroid.core.service.ServerCheckerJob; |
|
import org.transdroid.core.widget.ListWidgetProvider; |
|
import org.transdroid.daemon.Daemon; |
|
import org.transdroid.daemon.DaemonException; |
|
import org.transdroid.daemon.IDaemonAdapter; |
|
import org.transdroid.daemon.Priority; |
|
import org.transdroid.daemon.Torrent; |
|
import org.transdroid.daemon.TorrentDetails; |
|
import org.transdroid.daemon.TorrentFile; |
|
import org.transdroid.daemon.TorrentsSortBy; |
|
import org.transdroid.daemon.task.AddByFileTask; |
|
import org.transdroid.daemon.task.AddByMagnetUrlTask; |
|
import org.transdroid.daemon.task.AddByUrlTask; |
|
import org.transdroid.daemon.task.DaemonTaskFailureResult; |
|
import org.transdroid.daemon.task.DaemonTaskResult; |
|
import org.transdroid.daemon.task.DaemonTaskSuccessResult; |
|
import org.transdroid.daemon.task.ForceRecheckTask; |
|
import org.transdroid.daemon.task.GetFileListTask; |
|
import org.transdroid.daemon.task.GetFileListTaskSuccessResult; |
|
import org.transdroid.daemon.task.GetStatsTask; |
|
import org.transdroid.daemon.task.GetStatsTaskSuccessResult; |
|
import org.transdroid.daemon.task.GetTorrentDetailsTask; |
|
import org.transdroid.daemon.task.GetTorrentDetailsTaskSuccessResult; |
|
import org.transdroid.daemon.task.PauseTask; |
|
import org.transdroid.daemon.task.RemoveTask; |
|
import org.transdroid.daemon.task.ResumeTask; |
|
import org.transdroid.daemon.task.RetrieveTask; |
|
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; |
|
import org.transdroid.daemon.task.SetAlternativeModeTask; |
|
import org.transdroid.daemon.task.SetDownloadLocationTask; |
|
import org.transdroid.daemon.task.SetFilePriorityTask; |
|
import org.transdroid.daemon.task.SetLabelTask; |
|
import org.transdroid.daemon.task.SetTrackersTask; |
|
import org.transdroid.daemon.task.SetTransferRatesTask; |
|
import org.transdroid.daemon.task.StartTask; |
|
import org.transdroid.daemon.task.StopTask; |
|
import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask; |
|
import org.transdroid.daemon.task.ToggleSequentialDownloadTask; |
|
import org.transdroid.daemon.util.HttpHelper; |
|
|
|
import java.io.File; |
|
import java.io.FileNotFoundException; |
|
import java.io.FileOutputStream; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.io.UnsupportedEncodingException; |
|
import java.net.URLDecoder; |
|
import java.util.ArrayList; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Map.Entry; |
|
|
|
/** |
|
* Main activity that holds the fragment that shows the torrents list, presents a way to filter the list (via an action bar spinner or list side list) |
|
* and potentially shows a torrent details fragment too, if there is room. Task execution such as loading of and adding torrents is performs in this |
|
* activity, using background methods. Finally, the activity offers navigation elements such as access to settings and showing connection issues. |
|
* |
|
* @author Eric Kok |
|
*/ |
|
@EActivity(R.layout.activity_torrents) |
|
public class TorrentsActivity extends AppCompatActivity implements TorrentTasksExecutor, RefreshableActivity { |
|
|
|
private static final int RESULT_DETAILS = 0; |
|
|
|
// Fragment uses this to pause the refresh across restarts |
|
public boolean stopRefresh = false; |
|
|
|
// Navigation components |
|
@SystemService |
|
protected SearchManager searchManager; |
|
@Bean |
|
protected Log log; |
|
@Bean |
|
protected NavigationHelper navigationHelper; |
|
@Bean |
|
protected ConnectivityHelper connectivityHelper; |
|
@ViewById |
|
protected Toolbar selectionToolbar; |
|
@ViewById |
|
protected Toolbar torrentsToolbar; |
|
@ViewById |
|
protected Toolbar actionsToolbar; |
|
@ViewById(R.id.contextual_menu) |
|
protected ActionMenuView contextualMenu; |
|
@ViewById |
|
protected FloatingActionsMenu addmenuButton; |
|
@ViewById |
|
protected FloatingActionButton addmenuFileButton; |
|
@ViewById |
|
protected DrawerLayout drawerLayout; |
|
@ViewById(R.id.drawer_container) |
|
protected ViewGroup drawerContainer; |
|
@ViewById |
|
protected ListView drawerList; |
|
@ViewById |
|
protected ListView filtersList; |
|
@ViewById |
|
protected SearchView filterSearch; |
|
// Settings |
|
@Bean |
|
protected ApplicationSettings applicationSettings; |
|
@Bean |
|
protected SystemSettings systemSettings; |
|
@InstanceState |
|
protected NavigationFilter currentFilter = null; |
|
@InstanceState |
|
protected String preselectNavigationFilter = null; |
|
@InstanceState |
|
protected boolean turtleModeEnabled = false; |
|
@InstanceState |
|
protected ArrayList<Label> lastNavigationLabels; |
|
// Contained torrent and details fragments |
|
@FragmentById(R.id.torrents_fragment) |
|
protected TorrentsFragment fragmentTorrents; |
|
@FragmentById(R.id.torrentdetails_fragment) |
|
protected DetailsFragment fragmentDetails; |
|
@InstanceState |
|
boolean firstStart = true; |
|
private ListView navigationList; |
|
private FilterListAdapter navigationListAdapter; |
|
private ServerSelectionView serverSelectionView; |
|
private ServerStatusView serverStatusView; |
|
private ActionBarDrawerToggle drawerToggle; |
|
private MenuItem searchMenu = null; |
|
private IDaemonAdapter currentConnection = null; |
|
|
|
// Auto refresh task |
|
private AsyncTask<Void, Void, Void> autoRefreshTask; |
|
|
|
private String awaitingAddLocalFile; |
|
private String awaitingAddTitle; |
|
/** |
|
* Handles item selections on the dedicated list of filter items |
|
*/ |
|
private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() { |
|
@Override |
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
|
navigationList.setItemChecked(position, true); |
|
Object item = navigationList.getAdapter().getItem(position); |
|
if (item instanceof SimpleListItem) { |
|
filterSelected((SimpleListItem) item, false); |
|
} |
|
if (drawerLayout != null) |
|
drawerLayout.closeDrawer(drawerContainer); |
|
} |
|
}; |
|
private SearchView.OnQueryTextListener filterQueryTextChanged = new SearchView.OnQueryTextListener() { |
|
@Override |
|
public boolean onQueryTextSubmit(String query) { |
|
return false; |
|
} |
|
|
|
@Override |
|
public boolean onQueryTextChange(String newText) { |
|
// Redirect to filter method which will directly apply it |
|
filterTorrents(newText); |
|
return true; |
|
} |
|
}; |
|
|
|
@Override |
|
public void onCreate(Bundle savedInstanceState) { |
|
SettingsUtils.applyDayNightTheme(this); |
|
|
|
// Catch any uncaught exception to log it |
|
Thread.setDefaultUncaughtExceptionHandler(new LogUncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler())); |
|
super.onCreate(savedInstanceState); |
|
} |
|
|
|
@AfterViews |
|
protected void init() { |
|
|
|
// Use custom views as action bar content, showing filter selection and current torrent counts/speeds |
|
serverSelectionView = ServerSelectionView_.build(this); |
|
serverStatusView = ServerStatusView_.build(this); |
|
if (selectionToolbar != null) { |
|
selectionToolbar.addView(serverSelectionView); |
|
} else { |
|
torrentsToolbar.addView(serverSelectionView); |
|
} |
|
actionsToolbar.addView(serverStatusView); |
|
// Redirect to the classic activity implementation so we can use @OptionsItem methods |
|
actionsToolbar.setOnMenuItemClickListener(this::onOptionsItemSelected); |
|
setSupportActionBar(torrentsToolbar); // For direct menu item inflation by the contained fragments |
|
getSupportActionBar().setDisplayShowTitleEnabled(false); |
|
|
|
// Construct the filters list, i.e. the list of servers, status types and labels |
|
navigationListAdapter = FilterListAdapter_.getInstance_(this); |
|
navigationListAdapter.updateServers(applicationSettings.getAllServerSettings()); |
|
navigationListAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this)); |
|
// Add an empty labels list (which will be updated later, but the adapter needs to be created now) |
|
navigationListAdapter.updateLabels(new ArrayList<>()); |
|
|
|
// Apply the filters list to the navigation drawer (on phones) or the dedicated side bar (i.e. on tablets) |
|
if (filtersList != null) { |
|
navigationList = filtersList; |
|
} else { |
|
navigationList = drawerList; |
|
drawerToggle = |
|
new ActionBarDrawerToggle(this, drawerLayout, torrentsToolbar, R.string.navigation_opendrawer, R.string.navigation_closedrawer); |
|
drawerToggle.setDrawerIndicatorEnabled(true); |
|
drawerLayout.addDrawerListener(drawerToggle); |
|
} |
|
navigationList.setAdapter(navigationListAdapter); |
|
navigationList.setOnItemClickListener(onFilterListItemClicked); |
|
// Now that all items (or at least their adapters) have been added, ensure a filter is selected |
|
// NOTE When this is a fresh start, we might override the filter later (based on the last user selection) |
|
if (currentFilter == null) { |
|
currentFilter = StatusType.getShowAllType(this); |
|
} |
|
filterSearch.setOnQueryTextListener(filterQueryTextChanged); |
|
|
|
// Load the default server or a server that was explicitly supplied in the starting intent |
|
ServerSetting defaultServer = applicationSettings.getDefaultServer(); |
|
if (defaultServer == null) { |
|
// No server settings yet |
|
return; |
|
} |
|
Torrent openTorrent = null; |
|
if (getIntent().getAction() != null && getIntent().getAction().equals(ListWidgetProvider.INTENT_STARTSERVER) && |
|
getIntent().getExtras() == null && getIntent().hasExtra(ListWidgetProvider.EXTRA_SERVER)) { |
|
// A server settings order ID was provided in this org.transdroid.START_SERVER action intent |
|
int serverId = getIntent().getExtras().getInt(ListWidgetProvider.EXTRA_SERVER); |
|
if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) { |
|
log.e(this, "Tried to start with " + ListWidgetProvider.EXTRA_SERVER + " intent but " + serverId + |
|
" is not an existing server order id"); |
|
} else { |
|
defaultServer = applicationSettings.getServerSetting(serverId); |
|
if (getIntent().hasExtra(ListWidgetProvider.EXTRA_TORRENT)) { |
|
openTorrent = getIntent().getParcelableExtra(ListWidgetProvider.EXTRA_TORRENT); |
|
} |
|
} |
|
} |
|
|
|
// Connect to the last used server or a server that was explicitly supplied in the starting intent |
|
if (firstStart) { |
|
// Force first torrents refresh |
|
filterSelected(defaultServer, true); |
|
// Perhaps we can select the last used navigation filter, but only after a first refresh was completed |
|
preselectNavigationFilter = applicationSettings.getLastUsedNavigationFilter(); |
|
// Handle any start up intents |
|
if (openTorrent != null) { |
|
openDetails(openTorrent); |
|
} else if (getIntent() != null) { |
|
handleStartIntent(); |
|
} |
|
} else { |
|
// Resume after instead of fully loading the torrents list; create connection and set action bar title |
|
ServerSetting lastUsed = applicationSettings.getLastUsedServer(); |
|
currentConnection = lastUsed.getServerAdapter(connectivityHelper.getConnectedNetworkName(), this); |
|
serverSelectionView.updateCurrentServer(currentConnection); |
|
serverSelectionView.updateCurrentFilter(currentFilter); |
|
} |
|
firstStart = false; |
|
|
|
// Start the jobs for the background services, if needed |
|
ServerCheckerJob.schedule(getApplicationContext()); |
|
RssCheckerJob.schedule(getApplicationContext()); |
|
AppUpdateJob.schedule(getApplicationContext()); |
|
|
|
} |
|
|
|
@Override |
|
protected void onPostCreate(Bundle savedInstanceState) { |
|
super.onPostCreate(savedInstanceState); |
|
// Sync the toggle state after onRestoreInstanceState has occurred |
|
if (drawerToggle != null) { |
|
drawerToggle.syncState(); |
|
} |
|
} |
|
|
|
@Override |
|
protected void onResume() { |
|
super.onResume(); |
|
|
|
// update navigation labels |
|
navigationListAdapter.updateLabels(lastNavigationLabels); |
|
|
|
// Refresh server settings |
|
navigationListAdapter.updateServers(applicationSettings.getAllServerSettings()); |
|
ServerSetting lastUsed = applicationSettings.getLastUsedServer(); |
|
|
|
if (lastUsed == null) { |
|
// Still no settings |
|
updateFragmentVisibility(false); |
|
return; |
|
} |
|
|
|
// If we had no connection before, establish it now; otherwise just reload the settings |
|
if (currentConnection == null) { |
|
filterSelected(lastUsed, true); |
|
} else { |
|
currentConnection = lastUsed.getServerAdapter(connectivityHelper.getConnectedNetworkName(), this); |
|
} |
|
|
|
// Start auto refresh |
|
startAutoRefresh(); |
|
|
|
} |
|
|
|
@OnActivityResult(RESULT_DETAILS) |
|
protected void onDetailsScreenResult(Intent result) { |
|
// If the details activity returns whether the torrent was removed or updated, update the torrents list as well |
|
// (the details fragment is the source, so no need to update that) |
|
if (result != null && result.hasExtra("affected_torrent")) { |
|
Torrent affected = result.getParcelableExtra("affected_torrent"); |
|
fragmentTorrents.quickUpdateTorrent(affected, result.getBooleanExtra("torrent_removed", false)); |
|
} |
|
} |
|
|
|
public void startAutoRefresh() { |
|
// Check if already running |
|
if (autoRefreshTask != null || stopRefresh || systemSettings.getRefreshIntervalMilliseconds() == 0) { |
|
return; |
|
} |
|
|
|
autoRefreshTask = new AsyncTask<Void, Void, Void>() { |
|
@Override |
|
protected Void doInBackground(Void... params) { |
|
while (!isCancelled()) { |
|
try { |
|
Thread.sleep(systemSettings.getRefreshIntervalMilliseconds()); |
|
} catch (InterruptedException e) { |
|
// Ignore |
|
} |
|
// Just in case it was cancelled during sleep |
|
if (isCancelled()) { |
|
return null; |
|
} |
|
|
|
refreshTorrents(); |
|
if (Daemon.supportsStats(currentConnection.getType())) { |
|
getAdditionalStats(); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
}; |
|
autoRefreshTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|
} |
|
|
|
public void stopAutoRefresh() { |
|
if (autoRefreshTask != null) { |
|
autoRefreshTask.cancel(true); |
|
} |
|
autoRefreshTask = null; |
|
} |
|
|
|
@Override |
|
public boolean onCreateOptionsMenu(Menu menu) { |
|
super.onCreateOptionsMenu(menu); |
|
// Manually insert the actions into the main torrent and secondary actions toolbars |
|
torrentsToolbar.inflateMenu(R.menu.activity_torrents_main); |
|
if (actionsToolbar.getMenu().size() == 0) { |
|
actionsToolbar.inflateMenu(R.menu.activity_torrents_secondary); |
|
} |
|
if (navigationHelper.enableSearchUi()) { |
|
// Add an expandable SearchView to the action bar |
|
MenuItem item = menu.findItem(R.id.action_search); |
|
SearchView searchView = new SearchView(torrentsToolbar.getContext()); |
|
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); |
|
searchView.setQueryRefinementEnabled(true); |
|
searchView.setOnSearchClickListener(v -> { |
|
// Pause autorefresh |
|
stopRefresh = true; |
|
stopAutoRefresh(); |
|
}); |
|
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { |
|
@Override |
|
public boolean onMenuItemActionExpand(MenuItem item) { |
|
return true; |
|
} |
|
|
|
@Override |
|
public boolean onMenuItemActionCollapse(MenuItem item) { |
|
stopRefresh = false; |
|
startAutoRefresh(); |
|
return true; |
|
} |
|
}); |
|
item.setActionView(searchView); |
|
searchMenu = item; |
|
} |
|
return true; |
|
} |
|
|
|
@Override |
|
public boolean onPrepareOptionsMenu(Menu menu) { |
|
super.onPrepareOptionsMenu(menu); |
|
|
|
// No connection yet; hide all menu options except settings |
|
if (currentConnection == null) { |
|
torrentsToolbar.setNavigationIcon(null); |
|
if (selectionToolbar != null) |
|
selectionToolbar.setVisibility(View.GONE); |
|
addmenuButton.setVisibility(View.GONE); |
|
actionsToolbar.setVisibility(View.GONE); |
|
if (filtersList != null) |
|
filtersList.setVisibility(View.GONE); |
|
filterSearch.setVisibility(View.GONE); |
|
torrentsToolbar.getMenu().findItem(R.id.action_search).setVisible(false); |
|
torrentsToolbar.getMenu().findItem(R.id.action_rss).setVisible(false); |
|
torrentsToolbar.getMenu().findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); |
|
torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(true); |
|
actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(false); |
|
actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(false); |
|
actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(false); |
|
actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(false); |
|
if (fragmentTorrents != null) { |
|
fragmentTorrents.updateConnectionStatus(false, null); |
|
} |
|
return true; |
|
} |
|
|
|
// There is a connection (read: settings to some server known) |
|
if (drawerToggle != null) |
|
torrentsToolbar.setNavigationIcon(R.drawable.ic_action_drawer); |
|
if (selectionToolbar != null) |
|
selectionToolbar.setVisibility(View.VISIBLE); |
|
addmenuButton.setVisibility(View.VISIBLE); |
|
actionsToolbar.setVisibility(View.VISIBLE); |
|
if (filtersList != null) |
|
filtersList.setVisibility(View.VISIBLE); |
|
filterSearch.setVisibility(View.VISIBLE); |
|
boolean addByFile = Daemon.supportsAddByFile(currentConnection.getType()); |
|
addmenuFileButton.setVisibility(addByFile ? View.VISIBLE : View.GONE); |
|
// Primary toolbar menu |
|
torrentsToolbar.getMenu().findItem(R.id.action_search).setVisible(navigationHelper.enableSearchUi()); |
|
torrentsToolbar.getMenu().findItem(R.id.action_rss).setVisible(navigationHelper.enableRssUi()); |
|
torrentsToolbar.getMenu().findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); |
|
torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(false); |
|
// Secondary toolbar menu |
|
boolean hasAltMode = Daemon.supportsSetAlternativeMode(currentConnection.getType()); |
|
actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(hasAltMode && !turtleModeEnabled); |
|
actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(hasAltMode && turtleModeEnabled); |
|
actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(true); |
|
actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(true); |
|
actionsToolbar.getMenu().findItem(R.id.action_sort_added).setVisible(Daemon.supportsDateAdded(currentConnection.getType())); |
|
if (fragmentTorrents != null) { |
|
fragmentTorrents.updateConnectionStatus(true, currentConnection.getType()); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
@Override |
|
public boolean onOptionsItemSelected(MenuItem item) { |
|
// Handle only if this is the drawer toggle; otherwise the AndroidAnnotations will be used |
|
return drawerToggle != null && drawerToggle.onOptionsItemSelected(item); |
|
} |
|
|
|
/** |
|
* A new filter was selected; update the view over the current data |
|
* |
|
* @param item The touched filter item |
|
* @param forceNewConnection Whether a new connection should be initialised regardless of the old server selection |
|
*/ |
|
protected void filterSelected(SimpleListItem item, boolean forceNewConnection) { |
|
|
|
// No longer apply the last used filter (on a fresh application start), if we still needed to |
|
preselectNavigationFilter = null; |
|
|
|
// Server selection |
|
if (item instanceof ServerSetting) { |
|
ServerSetting server = (ServerSetting) item; |
|
|
|
if (!forceNewConnection && currentConnection != null && server.equals(currentConnection.getSettings())) { |
|
// Already connected to this server; just ask for a refresh instead |
|
fragmentTorrents.updateIsLoading(true); |
|
refreshTorrents(); |
|
return; |
|
} |
|
|
|
// Update connection to the newly selected server and refresh |
|
currentConnection = server.getServerAdapter(connectivityHelper.getConnectedNetworkName(), this); |
|
applicationSettings.setLastUsedServer(server); |
|
serverSelectionView.updateCurrentServer(currentConnection); |
|
if (forceNewConnection) { |
|
serverSelectionView.updateCurrentFilter(currentFilter); |
|
} |
|
|
|
// Clear the currently shown list of torrents and perhaps the details |
|
fragmentTorrents.clear(true, true); |
|
if (fragmentDetails != null && fragmentDetails.isResumed() && fragmentDetails.getActivity() != null) { |
|
fragmentDetails.updateIsLoading(false, null); |
|
fragmentDetails.clear(); |
|
fragmentDetails.setCurrentServerSettings(server); |
|
} |
|
updateFragmentVisibility(true); |
|
refreshScreen(); |
|
return; |
|
|
|
} |
|
|
|
// Status type or label selection - both of which are navigation filters |
|
if (item instanceof NavigationFilter) { |
|
// Set new filter |
|
currentFilter = (NavigationFilter) item; |
|
fragmentTorrents.applyNavigationFilter(currentFilter); |
|
serverSelectionView.updateCurrentFilter(currentFilter); |
|
// Remember that the user last selected this |
|
applicationSettings.setLastUsedNavigationFilter(currentFilter); |
|
// Clear the details view |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.updateIsLoading(false, null); |
|
fragmentDetails.clear(); |
|
} |
|
} |
|
|
|
} |
|
|
|
/** |
|
* Hides the filter list and details fragment's full view if there is no configured connection |
|
* |
|
* @param hasServerSettings Whether there are server settings available, so we can continue to connect |
|
*/ |
|
private void updateFragmentVisibility(boolean hasServerSettings) { |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
if (hasServerSettings) { |
|
getSupportFragmentManager().beginTransaction().show(fragmentDetails).commit(); |
|
} else { |
|
getSupportFragmentManager().beginTransaction().hide(fragmentDetails).commit(); |
|
} |
|
} |
|
invalidateOptionsMenu(); |
|
} |
|
|
|
@Override |
|
protected void onNewIntent(Intent intent) { |
|
super.onNewIntent(intent); |
|
setIntent(intent); |
|
handleStartIntent(); |
|
} |
|
|
|
protected void handleStartIntent() { |
|
// For intents that come from out of the application, perhaps we can not directly add them |
|
if (applicationSettings.getDefaultServerKey() == ApplicationSettings.DEFAULTSERVER_ASKONADD && getIntent().getData() != null) { |
|
// First ask which server to use before adding any intent from the extras |
|
ServerPickerDialog.startServerPicker(this, applicationSettings.getAllServerSettings()); |
|
return; |
|
} |
|
addFromIntent(); |
|
} |
|
|
|
public void switchServerAndAddFromIntent(int position) { |
|
// Callback from the ServerPickerDialog; force a connection before selecting it (in the navigation) |
|
// Note: we can just use the list position as we have stable server setting ids |
|
ServerSetting selectedServer = applicationSettings.getAllServerSettings().get(position); |
|
filterSelected(selectedServer, false); |
|
addFromIntent(); |
|
} |
|
|
|
/** |
|
* If required, add torrents from the supplied intent extras. |
|
*/ |
|
protected void addFromIntent() { |
|
Intent intent = getIntent(); |
|
Uri dataUri = intent.getData(); |
|
String data = intent.getDataString(); |
|
String action = intent.getAction(); |
|
|
|
// Adding multiple torrents at the same time (as found in the Intent extras Bundle) |
|
if (action != null && action.equals("org.transdroid.ADD_MULTIPLE")) { |
|
// Intent should have some extras pointing to possibly multiple torrents |
|
String[] urls = intent.getStringArrayExtra("TORRENT_URLS"); |
|
String[] titles = intent.getStringArrayExtra("TORRENT_TITLES"); |
|
if (urls != null) { |
|
for (int i = 0; i < urls.length; i++) { |
|
String title = (titles != null && titles.length >= i ? titles[i] : NavigationHelper.extractNameFromUri(Uri.parse(urls[i]))); |
|
if (intent.hasExtra("PRIVATE_SOURCE")) { |
|
// This is marked by the Search Module as being a private source site; get the url locally first |
|
addTorrentFromPrivateSource(urls[i], title, intent.getStringExtra("PRIVATE_SOURCE")); |
|
} else { |
|
addTorrentByUrl(urls[i], title); |
|
} |
|
} |
|
} |
|
return; |
|
} |
|
|
|
// Add a torrent from a local or remote data URI? |
|
if (dataUri == null) { |
|
return; |
|
} |
|
if (dataUri.getScheme() == null) { |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_invalid_url_form).colorResource(R.color.red)); |
|
return; |
|
} |
|
|
|
// Get torrent title |
|
String title = NavigationHelper.extractNameFromUri(dataUri); |
|
if (intent.hasExtra("TORRENT_TITLE")) { |
|
title = intent.getStringExtra("TORRENT_TITLE"); |
|
} |
|
|
|
// Adding a torrent from the Android downloads manager |
|
if (dataUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { |
|
addTorrentFromDownloads(dataUri, title); |
|
return; |
|
} |
|
|
|
// Adding a torrent from http or https URL |
|
if (dataUri.getScheme().equals("http") || dataUri.getScheme().equals("https")) { |
|
|
|
String privateSource = getIntent().getStringExtra("PRIVATE_SOURCE"); |
|
|
|
WebsearchSetting match = null; |
|
if (privateSource == null) { |
|
// Check if the target URL is also defined as a web search in the user's settings |
|
List<WebsearchSetting> websearches = applicationSettings.getWebsearchSettings(); |
|
for (WebsearchSetting setting : websearches) { |
|
Uri uri = Uri.parse(setting.getBaseUrl()); |
|
if (uri.getHost() != null && uri.getHost().equals(dataUri.getHost())) { |
|
match = setting; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// If the URL is also a web search and it defines cookies, use the cookies by downloading the targeted |
|
// torrent file (while supplies the cookies to the HTTP request) instead of sending the URL directly to the |
|
// torrent client. If instead it is marked (by the Torrent Search module) as being form a private site, use |
|
// the Search Module instead to download the url locally first. |
|
if (match != null && match.getCookies() != null) { |
|
addTorrentFromWeb(data, match, title); |
|
} else if (privateSource != null) { |
|
addTorrentFromPrivateSource(data, title, privateSource); |
|
} else { |
|
// Normally send the URL to the torrent client |
|
addTorrentByUrl(data, title); |
|
} |
|
return; |
|
} |
|
|
|
// Adding a torrent from magnet URL |
|
if (dataUri.getScheme().equals("magnet")) { |
|
addTorrentByMagnetUrl(data, title); |
|
return; |
|
} |
|
|
|
// Adding a local .torrent file; the title we show is just the file name |
|
if (dataUri.getScheme().equals("file")) { |
|
addTorrentByFile(data, title); |
|
} |
|
|
|
} |
|
|
|
@Override |
|
protected void onPause() { |
|
if (searchMenu != null) { |
|
searchMenu.collapseActionView(); |
|
} |
|
stopAutoRefresh(); |
|
super.onPause(); |
|
} |
|
|
|
@Override |
|
public boolean onSearchRequested() { |
|
if (searchMenu != null) { |
|
searchMenu.expandActionView(); |
|
} |
|
return true; |
|
} |
|
|
|
@Override |
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
|
if (awaitingAddLocalFile != null && awaitingAddTitle != null && |
|
Boolean.TRUE.equals(navigationHelper.handleTorrentReadPermissionResult(requestCode, grantResults))) { |
|
addTorrentByFile(awaitingAddLocalFile, awaitingAddTitle); |
|
} |
|
} |
|
|
|
@Click(R.id.addmenu_link_button) |
|
protected void startUrlEntryDialog() { |
|
addmenuButton.collapse(); |
|
UrlEntryDialog.show(this); |
|
} |
|
|
|
@Click(R.id.addmenu_file_button) |
|
protected void startFilePicker() { |
|
addmenuButton.collapse(); |
|
FilePickerHelper.startFilePicker(this); |
|
} |
|
|
|
@Background |
|
@OnActivityResult(FilePickerHelper.ACTIVITY_FILEPICKER) |
|
public void onFilePicked(int resultCode, Intent data) { |
|
// We should have received an Intent with a local torrent's Uri as data from the file picker |
|
if (data != null && data.getData() != null && !data.getData().toString().equals("")) { |
|
Uri dataUri = data.getData(); |
|
|
|
// Get torrent title |
|
String title = NavigationHelper.extractNameFromUri(dataUri); |
|
|
|
// Adding a torrent from the via a content:// scheme (access through content provider stream) |
|
if (dataUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { |
|
addTorrentFromDownloads(dataUri, title); |
|
return; |
|
} |
|
|
|
// Adding a .torrent file directly via the file:// scheme (we can access it directly) |
|
if (dataUri.getScheme().equals("file")) { |
|
addTorrentByFile(data.getDataString(), title); |
|
} |
|
|
|
} |
|
} |
|
|
|
@OptionsItem(R.id.action_refresh) |
|
public void refreshScreen() { |
|
if (fragmentTorrents.isAdded()) |
|
fragmentTorrents.updateIsLoading(true); |
|
refreshTorrents(); |
|
if (Daemon.supportsStats(currentConnection.getType())) { |
|
getAdditionalStats(); |
|
} |
|
} |
|
|
|
@OptionsItem(R.id.action_enableturtle) |
|
protected void enableTurtleMode() { |
|
updateTurtleMode(true); |
|
} |
|
|
|
@OptionsItem(R.id.action_disableturtle) |
|
protected void disableTurtleMode() { |
|
updateTurtleMode(false); |
|
} |
|
|
|
@OptionsItem(R.id.action_rss) |
|
protected void openRss() { |
|
RssFeedsActivity_.intent(this).start(); |
|
} |
|
|
|
@OptionsItem(R.id.action_settings) |
|
protected void openSettings() { |
|
MainSettingsActivity_.intent(this).start(); |
|
} |
|
|
|
@OptionsItem(R.id.action_help) |
|
protected void openHelp() { |
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/download/"))); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_byname) |
|
protected void sortByName() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.Alphanumeric); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_status) |
|
protected void sortByStatus() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.Status); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_done) |
|
protected void sortByDateDone() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.DateDone); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_added) |
|
protected void sortByDateAdded() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.DateAdded); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_percent) |
|
protected void sortByPercent() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.Percent); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_downspeed) |
|
protected void sortByDownspeed() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.DownloadSpeed); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_upspeed) |
|
protected void sortByUpspeed() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.UploadSpeed); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_ratio) |
|
protected void sortByRatio() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.Ratio); |
|
} |
|
|
|
@OptionsItem(R.id.action_sort_size) |
|
protected void sortBySize() { |
|
fragmentTorrents.sortBy(TorrentsSortBy.Size); |
|
} |
|
|
|
/** |
|
* Redirect the newly entered list filter to the torrents fragment. |
|
* |
|
* @param newFilterText The newly entered filter (or empty to clear the current filter). |
|
*/ |
|
public void filterTorrents(String newFilterText) { |
|
fragmentTorrents.applyTextFilter(newFilterText); |
|
} |
|
|
|
/** |
|
* Shows the a details fragment for the given torrent, either in the dedicated details fragment pane, in the same pane as the torrent list was |
|
* displayed or by starting a details activity. |
|
* |
|
* @param torrent The torrent to show detailed statistics for |
|
*/ |
|
public void openDetails(Torrent torrent) { |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.updateTorrent(torrent); |
|
} else { |
|
DetailsActivity_.intent(this).torrent(torrent).currentLabels(lastNavigationLabels).startForResult(RESULT_DETAILS); |
|
} |
|
} |
|
|
|
@Background |
|
protected void refreshTorrents() { |
|
String startConnectionId = currentConnection.getSettings().getIdString(); |
|
DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log); |
|
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { |
|
// During the command execution the user changed the server, so we are no longer interested in the result |
|
return; |
|
} |
|
if (result instanceof RetrieveTaskSuccessResult) { |
|
onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels()); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, true); |
|
} |
|
} |
|
|
|
@Background |
|
public void refreshTorrentDetails(Torrent torrent) { |
|
if (!Daemon.supportsFineDetails(currentConnection.getType())) { |
|
return; |
|
} |
|
String startConnectionId = currentConnection.getSettings().getIdString(); |
|
DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log); |
|
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { |
|
// During the command execution the user changed the server, so we are no longer interested in the result |
|
return; |
|
} |
|
if (result instanceof GetTorrentDetailsTaskSuccessResult) { |
|
onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails()); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
public void refreshTorrentFiles(Torrent torrent) { |
|
if (!Daemon.supportsFileListing(currentConnection.getType())) { |
|
return; |
|
} |
|
String startConnectionId = currentConnection.getSettings().getIdString(); |
|
DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log); |
|
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { |
|
// During the command execution the user changed the server, so we are no longer interested in the result |
|
return; |
|
} |
|
if (result instanceof GetFileListTaskSuccessResult) { |
|
onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles()); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
protected void getAdditionalStats() { |
|
String startConnectionId = currentConnection.getSettings().getIdString(); |
|
DaemonTaskResult result = GetStatsTask.create(currentConnection).execute(log); |
|
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { |
|
// During the command execution the user changed the server, so we are no longer interested in the result |
|
return; |
|
} |
|
if (result instanceof GetStatsTaskSuccessResult) { |
|
onTurtleModeRetrieved(((GetStatsTaskSuccessResult) result).isAlternativeModeEnabled()); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
protected void updateTurtleMode(boolean enable) { |
|
String startConnectionId = currentConnection.getSettings().getIdString(); |
|
DaemonTaskResult result = SetAlternativeModeTask.create(currentConnection, enable).execute(log); |
|
if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) { |
|
// During the command execution the user changed the server, so we are no longer interested in the result |
|
return; |
|
} |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
// Success; no need to retrieve it again - just update the visual indicator |
|
onTurtleModeRetrieved(enable); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
public void addTorrentByUrl(String url, String title) { |
|
DaemonTaskResult result = AddByUrlTask.create(currentConnection, url, title).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); |
|
refreshTorrents(); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
public void addTorrentByMagnetUrl(String url, String title) { |
|
|
|
// Since v39 Chrome sends application/x-www-form-urlencoded magnet links and most torrent clients do not understand those, so decode first |
|
try { |
|
url = URLDecoder.decode(url, "UTF-8"); |
|
} catch (UnsupportedEncodingException e) { |
|
// Ignore: UTF-8 is always available on Android devices |
|
} catch (IllegalArgumentException e) { |
|
// Illegal character or escape sequence; fail task to show error |
|
onCommunicationError(new DaemonTaskFailureResult(AddByMagnetUrlTask.create(currentConnection, url), |
|
new DaemonException(DaemonException.ExceptionType.MalformedUri, "Invalid characters in magnet uri")), false); |
|
return; |
|
} |
|
|
|
AddByMagnetUrlTask addByMagnetUrlTask = AddByMagnetUrlTask.create(currentConnection, url); |
|
if (!Daemon.supportsAddByMagnetUrl(currentConnection.getType())) { |
|
// No support for magnet links: forcefully let the task fail to report the error |
|
onCommunicationError(new DaemonTaskFailureResult(addByMagnetUrlTask, new DaemonException(DaemonException.ExceptionType.MethodUnsupported, |
|
currentConnection.getType().name() + " does not support magnet links")), false); |
|
return; |
|
} |
|
|
|
DaemonTaskResult result = addByMagnetUrlTask.execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); |
|
refreshTorrents(); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
|
|
} |
|
|
|
@Background |
|
protected void addTorrentByFile(String localFile, String title) { |
|
// TODO EKO |
|
// if (!navigationHelper.checkTorrentReadPermission(this)) { |
|
// // No read permission yet (which we get the result of in onRequestPermissionsResult) |
|
// awaitingAddLocalFile = localFile; |
|
// awaitingAddTitle = title; |
|
// return; |
|
// } |
|
DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); |
|
refreshTorrents(); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
private void addTorrentFromDownloads(Uri contentUri, String title) { |
|
|
|
try { |
|
// Open the content uri as input stream and this via a local temporary file |
|
addTorrentFromStream(getContentResolver().openInputStream(contentUri), title); |
|
} catch (SecurityException e) { |
|
// No longer access to this file |
|
log.e(this, "No access given to " + contentUri.toString() + ": " + e.toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red)); |
|
} catch (FileNotFoundException e) { |
|
log.e(this, contentUri.toString() + " does not exist: " + e.toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red)); |
|
} |
|
} |
|
|
|
@Background |
|
protected void addTorrentFromPrivateSource(String url, String title, String source) { |
|
|
|
try { |
|
InputStream input = SearchHelper_.getInstance_(this).getFile(source, url); |
|
addTorrentFromStream(input, title); |
|
} catch (Exception e) { |
|
log.e(this, "Can't download private site torrent " + url + " from " + source + ": " + e.toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red)); |
|
} |
|
|
|
} |
|
|
|
@Background |
|
protected void addTorrentFromWeb(String url, WebsearchSetting websearchSetting, String title) { |
|
|
|
try { |
|
// Cookies are taken from the websearchSetting that we already matched against this target URL |
|
DefaultHttpClient httpclient = HttpHelper.createStandardHttpClient(false, null, null, null, true, null, 10000, null, -1); |
|
Map<String, String> cookies = HttpHelper.parseCookiePairs(websearchSetting.getCookies()); |
|
String domain = Uri.parse(url).getHost(); |
|
for (Entry<String, String> pair : cookies.entrySet()) { |
|
BasicClientCookie cookie = new BasicClientCookie(pair.getKey(), pair.getValue()); |
|
cookie.setPath("/"); |
|
cookie.setDomain(domain); |
|
httpclient.getCookieStore().addCookie(cookie); |
|
} |
|
|
|
// Download the torrent at the specified URL (which will first be written to a temporary file) |
|
// If we get an HTTP 401, 403 or 404 response, show an error to the user |
|
HttpResponse response = httpclient.execute(new HttpGet(url)); |
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED || |
|
response.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN || |
|
response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) { |
|
log.e(this, "Can't retrieve web torrent " + url + ": Unexpected HTTP response status code " + |
|
response.getStatusLine().toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_401).colorResource(R.color.red)); |
|
return; |
|
} |
|
InputStream input = response.getEntity().getContent(); |
|
addTorrentFromStream(input, title); |
|
} catch (Exception e) { |
|
log.e(this, "Can't retrieve web torrent " + url + ": " + e.toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red)); |
|
} |
|
} |
|
|
|
@Background |
|
protected void addTorrentFromStream(InputStream input, String title) { |
|
|
|
File tempFile = new File("/not/yet/set"); |
|
try { |
|
// Write a temporary file with the torrent contents |
|
tempFile = File.createTempFile("transdroid_", ".torrent", getCacheDir()); |
|
try (FileOutputStream output = new FileOutputStream(tempFile)) { |
|
final byte[] buffer = new byte[1024]; |
|
int read; |
|
while ((read = input.read(buffer)) != -1) { |
|
output.write(buffer, 0, read); |
|
} |
|
output.flush(); |
|
String fileName = Uri.fromFile(tempFile).toString(); |
|
addTorrentByFile(fileName, title); |
|
} |
|
} catch (IOException e) { |
|
log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red)); |
|
} finally { |
|
try { |
|
if (input != null) { |
|
input.close(); |
|
} |
|
} catch (IOException e) { |
|
log.e(this, "Error closing the input stream " + tempFile.toString() + ": " + e.toString()); |
|
SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red)); |
|
} |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void resumeTorrent(Torrent torrent) { |
|
torrent.mimicResume(); |
|
DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName())); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void pauseTorrent(Torrent torrent) { |
|
torrent.mimicPause(); |
|
DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName())); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void startTorrent(Torrent torrent, boolean forced) { |
|
torrent.mimicStart(); |
|
DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName())); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void stopTorrent(Torrent torrent) { |
|
torrent.mimicStop(); |
|
DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName())); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void removeTorrent(Torrent torrent, boolean withData) { |
|
DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, |
|
getString(withData ? R.string.result_removed_with_data : R.string.result_removed, torrent.getName())); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void updateLabel(Torrent torrent, String newLabel) { |
|
torrent.mimicNewLabel(newLabel); |
|
DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, |
|
newLabel == null ? getString(R.string.result_labelremoved) : getString(R.string.result_labelset, newLabel)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) { |
|
torrent.mimicSequentialDownload(sequentialState); |
|
DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) { |
|
torrent.mimicFirstLastPieceDownload(firstLastPieceState); |
|
DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.action_toggle_firstlastpiece)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void forceRecheckTorrent(Torrent torrent) { |
|
torrent.mimicCheckingStatus(); |
|
DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_recheckedstarted, torrent.getName())); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void updateTrackers(Torrent torrent, List<String> newTrackers) { |
|
DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void updateLocation(Torrent torrent, String newLocation) { |
|
DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
@Override |
|
public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) { |
|
DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<>(files)).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@Background |
|
public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) { |
|
DaemonTaskResult result = SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed).execute(log); |
|
if (result instanceof DaemonTaskSuccessResult) { |
|
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset)); |
|
} else { |
|
onCommunicationError((DaemonTaskFailureResult) result, false); |
|
} |
|
} |
|
|
|
@UiThread |
|
protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) { |
|
// Refresh the screen as well |
|
refreshScreen(); |
|
SnackbarManager.show(Snackbar.with(this).text(successMessage)); |
|
} |
|
|
|
@UiThread |
|
protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) { |
|
log.i(this, result.getException().toString()); |
|
String error = getString(LocalTorrent.getResourceForDaemonException(result.getException())); |
|
SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE)); |
|
fragmentTorrents.updateIsLoading(false); |
|
if (isCritical) { |
|
fragmentTorrents.updateError(error); |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.updateIsLoading(false, error); |
|
} |
|
} |
|
} |
|
|
|
@UiThread |
|
protected void onTorrentsRetrieved(List<Torrent> torrents, List<org.transdroid.daemon.Label> labels) { |
|
|
|
lastNavigationLabels = Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled)); |
|
|
|
// Report the newly retrieved list of torrents to the torrents fragment |
|
fragmentTorrents.updateIsLoading(false); |
|
fragmentTorrents.updateTorrents(new ArrayList<>(torrents), lastNavigationLabels); |
|
|
|
// Update the details fragment if the currently shown torrent is in the newly retrieved list |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.perhapsUpdateTorrent(torrents); |
|
} |
|
|
|
// Update local list of labels in the navigation |
|
navigationListAdapter.updateLabels(lastNavigationLabels); |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.updateLabels(lastNavigationLabels); |
|
} |
|
|
|
// Perhaps we were still waiting to preselect the last used filter (on a fresh application start) |
|
if (preselectNavigationFilter != null && navigationListAdapter != null) { |
|
for (int i = 0; i < navigationListAdapter.getCount(); i++) { |
|
// Look up the navigation filter item, which is represented as simple list item (and might not exist any |
|
// more, such as with a label that is deleted on the server) |
|
Object item = navigationListAdapter.getItem(i); |
|
if (item instanceof SimpleListItem && item instanceof NavigationFilter && |
|
((NavigationFilter) item).getCode().equals(preselectNavigationFilter)) { |
|
filterSelected((SimpleListItem) item, false); |
|
break; |
|
} |
|
} |
|
// Only preselect after the first update we receive (even if the filter wasn't found any more) |
|
preselectNavigationFilter = null; |
|
} |
|
|
|
// Update the server status (counts and speeds) in the action bar |
|
serverStatusView |
|
.updateStatus(torrents, systemSettings.treatDormantAsInactive(), Daemon.supportsSetTransferRates(currentConnection.getType())); |
|
|
|
} |
|
|
|
@UiThread |
|
protected void onTorrentDetailsRetrieved(Torrent torrent, TorrentDetails torrentDetails) { |
|
// Update the details fragment with the new fine details for the shown torrent |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.updateTorrentDetails(torrent, torrentDetails); |
|
} |
|
} |
|
|
|
@UiThread |
|
protected void onTorrentFilesRetrieved(Torrent torrent, List<TorrentFile> torrentFiles) { |
|
// Update the details fragment with the newly retrieved list of files |
|
if (fragmentDetails != null && fragmentDetails.isResumed()) { |
|
fragmentDetails.updateTorrentFiles(torrent, new ArrayList<>(torrentFiles)); |
|
} |
|
} |
|
|
|
@UiThread |
|
protected void onTurtleModeRetrieved(boolean turtleModeEnabled) { |
|
this.turtleModeEnabled = turtleModeEnabled; |
|
invalidateOptionsMenu(); |
|
} |
|
|
|
}
|
|
|