Java tutorial
/** * Copyright (C) Azureus Software, Inc, All Rights Reserved. * * 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 * of the License, 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.vuze.android.remote.rpc; import java.util.*; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.conn.HttpHostConnectException; import android.app.Activity; import android.util.Log; import com.vuze.android.remote.*; import com.vuze.util.JSONUtils; import com.vuze.util.MapUtils; @SuppressWarnings("rawtypes") public class TransmissionRPC { private class ReplyMapReceivedListenerWithRefresh implements ReplyMapReceivedListener { private final ReplyMapReceivedListener l; private final long[] ids; private List<String> fields; private int[] fileIndexes; private String[] fileFields; private String callID; private ReplyMapReceivedListenerWithRefresh(String callID, ReplyMapReceivedListener l, long[] ids) { this.callID = callID; this.l = l; this.ids = ids; this.fields = getBasicTorrentFieldIDs(); } public ReplyMapReceivedListenerWithRefresh(String callID, ReplyMapReceivedListener l, long[] torrentIDs, int[] fileIndexes, String[] fileFields) { this.callID = callID; this.l = l; this.ids = torrentIDs; this.fileIndexes = fileIndexes; this.fileFields = fileFields; this.fields = getFileInfoFields(); } @Override public void rpcSuccess(String id, Map optionalMap) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { } getTorrents(callID, ids, fields, fileIndexes, fileFields, null); } }).start(); if (l != null) { l.rpcSuccess(id, optionalMap); } } @Override public void rpcFailure(String id, String message) { if (l != null) { l.rpcFailure(id, message); } } @Override public void rpcError(String id, Exception e) { if (l != null) { l.rpcError(id, e); } } } private static final String TAG = "RPC"; // From Transmission's rpcimp.c :( // #define RECENTLY_ACTIVE_SECONDS 60 protected static final long RECENTLY_ACTIVE_MS = 60 * 1000l; private String rpcURL; private UsernamePasswordCredentials creds; protected Header[] headers; private int rpcVersion; private int rpcVersionAZ; private Boolean hasFileCountField = null; private List<String> basicTorrentFieldIDs; private final List<TorrentListReceivedListener> torrentListReceivedListeners = new ArrayList<>(); private final List<SessionSettingsReceivedListener> sessionSettingsReceivedListeners = new ArrayList<>(); protected Map latestSessionSettings; protected long lastRecentTorrentGet; private int cacheBuster = new Random().nextInt(); private SessionInfo sessionInfo; protected boolean supportsGZIP; private String[] defaultFileFields = {}; protected boolean supportsRCM; private boolean supportsTorrentRename; private boolean supportsTags; public TransmissionRPC(SessionInfo sessionInfo, String rpcURL, String username, String ac) { this.sessionInfo = sessionInfo; if (username != null) { creds = new UsernamePasswordCredentials(username, ac); } this.rpcURL = rpcURL; updateSessionSettings(ac); } public void getSessionStats(String[] fields, ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", "session-stats"); if (fields != null) { Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("fields", fields); } sendRequest("session-stats", map, l); } private void updateSessionSettings(String id) { Map<String, Object> map = new HashMap<>(); map.put("method", "session-get"); sendRequest(id, map, new ReplyMapReceivedListener() { @Override public void rpcSuccess(String id, Map map) { synchronized (sessionSettingsReceivedListeners) { latestSessionSettings = map; rpcVersion = MapUtils.getMapInt(map, "rpc-version", -1); rpcVersionAZ = MapUtils.getMapInt(map, "az-rpc-version", -1); if (rpcVersionAZ < 0 && map.containsKey("az-version")) { rpcVersionAZ = 0; } if (rpcVersionAZ >= 2) { hasFileCountField = true; } List listSupports = MapUtils.getMapList(map, "rpc-supports", null); supportsGZIP = listSupports != null && listSupports.contains("rpc:receive-gzip"); supportsRCM = listSupports != null && listSupports.contains("method:rcm-set-enabled"); supportsTorrentRename = listSupports != null && listSupports.contains("field:torrent-set-name"); supportsTags = listSupports != null && listSupports.contains("method:tags-get-list"); if (AndroidUtils.DEBUG_RPC) { Log.d(TAG, "Received Session-Get. " + map); } for (SessionSettingsReceivedListener l : sessionSettingsReceivedListeners) { l.sessionPropertiesUpdated(map); } } } @Override public void rpcFailure(String id, String message) { } @Override public void rpcError(String id, Exception e) { Activity activity = sessionInfo.getCurrentActivity(); if (activity != null) { AndroidUtils.showConnectionError(activity, e, false); } SessionInfoManager.removeSessionInfo(sessionInfo.getRemoteProfile().getID()); } }); } public void addTorrentByUrl(String url, boolean addPaused, final TorrentAddedReceivedListener l) { addTorrent(false, url, addPaused, l); } public void addTorrentByMeta(String torrentData, boolean addPaused, final TorrentAddedReceivedListener l) { addTorrent(true, torrentData, addPaused, l); } private void addTorrent(boolean isTorrentData, String data, boolean addPaused, final TorrentAddedReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-add"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("paused", addPaused); String id; if (isTorrentData) { id = "addTorrentByMeta"; mapArguments.put("metainfo", data); } else { id = "addTorrentByUrl"; mapArguments.put("filename", data); } //download-dir sendRequest(id, map, new ReplyMapReceivedListener() { @Override public void rpcSuccess(String id, Map optionalMap) { Map mapTorrentAdded = MapUtils.getMapMap(optionalMap, "torrent-added", null); if (mapTorrentAdded != null) { l.torrentAdded(mapTorrentAdded, false); return; } Map mapTorrentDupe = MapUtils.getMapMap(optionalMap, "torrent-duplicate", null); if (mapTorrentDupe != null) { l.torrentAdded(mapTorrentDupe, true); return; } } @Override public void rpcFailure(String id, String message) { l.torrentAddFailed(message); } @Override public void rpcError(String id, Exception e) { l.torrentAddError(e); } }); } public void getAllTorrents(String callID, TorrentListReceivedListener l) { getTorrents(callID, null, getBasicTorrentFieldIDs(), null, null, l); } public void getTorrent(String callID, long torrentID, List<String> fields, TorrentListReceivedListener l) { getTorrents(callID, new long[] { torrentID }, fields, null, null, l); } private void getTorrents(final String callID, final Object ids, List<String> fields, final int[] fileIndexes, String[] fileFields, final TorrentListReceivedListener l) { Map<String, Object> map = new HashMap<>(2); map.put("method", "torrent-get"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("fields", fields); if (ids != null) { mapArguments.put("ids", ids); } mapArguments.put("base-url", sessionInfo.getBaseURL()); if (rpcVersionAZ >= 3) { if (fields == null || fields.contains(TransmissionVars.FIELD_TORRENT_FILES)) { if (fileFields != null) { mapArguments.put("file-fields", fileFields); } else { mapArguments.put("file-fields", defaultFileFields); } if (fields != null) { fields.remove(TransmissionVars.FIELD_TORRENT_FILESTATS); } // build "hc" long[] torrentIDs = {}; if (ids instanceof long[]) { torrentIDs = (long[]) ids; } else if (ids instanceof Number) { torrentIDs = new long[] { ((Number) ids).longValue() }; } for (long torrentID : torrentIDs) { if (fileIndexes != null) { mapArguments.put("file-indexes-" + torrentID, fileIndexes); } Map<?, ?> mapTorrent = sessionInfo.getTorrent(torrentID); if (mapTorrent != null) { List listFiles = MapUtils.getMapList(mapTorrent, TransmissionVars.FIELD_TORRENT_FILES, null); if (listFiles != null) { List<Object> listHCs = new ArrayList<>(); for (int i = 0; i < listFiles.size(); i++) { Map mapFIle = (Map) listFiles.get(i); listHCs.add(mapFIle.get("hc")); } mapArguments.put("files-hc-" + torrentID, listHCs); } } } } } String idList = (ids instanceof long[]) ? Arrays.toString(((long[]) ids)) : "" + ids; sendRequest("getTorrents t=" + idList + "/f=" + Arrays.toString(fileIndexes) + ", " + (fields == null ? "null" : fields.size()) + "/" + (fileFields == null ? "null" : fileFields.length), map, new ReplyMapReceivedListener() { @SuppressWarnings({ "unchecked", }) @Override public void rpcSuccess(String id, Map optionalMap) { List list = MapUtils.getMapList(optionalMap, "torrents", Collections.EMPTY_LIST); if (hasFileCountField == null || !hasFileCountField) { for (Object o : list) { if (!(o instanceof Map)) { continue; } Map map = (Map) o; if (map.containsKey(TransmissionVars.FIELD_TORRENT_FILE_COUNT)) { hasFileCountField = true; continue; } int fileCount = MapUtils.getMapList(map, TransmissionVars.FIELD_TORRENT_PRIORITIES, Collections.EMPTY_LIST).size(); if (fileCount > 0) { map.put(TransmissionVars.FIELD_TORRENT_FILE_COUNT, fileCount); } } } // TODO: If we request a list of torrent IDs, and we don't get them // back on "success", then we should populate the listRemoved List listRemoved = MapUtils.getMapList(optionalMap, "removed", null); if (l != null) { l.rpcTorrentListReceived(callID, list, null); } TorrentListReceivedListener[] listReceivedListeners = getTorrentListReceivedListeners(); for (TorrentListReceivedListener torrentListReceivedListener : listReceivedListeners) { torrentListReceivedListener.rpcTorrentListReceived(callID, list, listRemoved); } } @Override public void rpcFailure(String id, String message) { // send event to listeners on fail/error // some do a call for a specific torrentID and rely on a response // of some sort to clean up (ie. files view progress bar), so // we must fake a reply with those torrentIDs List list = createFakeList(ids); if (l != null) { l.rpcTorrentListReceived(callID, list, null); } TorrentListReceivedListener[] listReceivedListeners = getTorrentListReceivedListeners(); for (TorrentListReceivedListener torrentListReceivedListener : listReceivedListeners) { torrentListReceivedListener.rpcTorrentListReceived(callID, list, null); } if (AndroidUtils.DEBUG_RPC) { Log.d(TAG, id + "] rpcFailure. fake listener for " + listReceivedListeners.length + "/" + (l == null ? 0 : 1) + ", " + list); } } private List createFakeList(Object ids) { List<Map> list = new ArrayList<>(); if (ids instanceof Long) { HashMap<String, Object> map = new HashMap<>(2); map.put("id", ids); list.add(map); return list; } if (ids instanceof long[]) { for (long torrentID : (long[]) ids) { HashMap<String, Object> map = new HashMap<>(2); map.put("id", torrentID); list.add(map); } } return list; } @Override public void rpcError(String id, Exception e) { // send event to listeners on fail/error // some do a call for a specific torrentID and rely on a response // of some sort to clean up (ie. files view progress bar), so // we must fake a reply with those torrentIDs List list = createFakeList(ids); if (l != null) { l.rpcTorrentListReceived(callID, list, null); } TorrentListReceivedListener[] listReceivedListeners = getTorrentListReceivedListeners(); for (TorrentListReceivedListener torrentListReceivedListener : listReceivedListeners) { torrentListReceivedListener.rpcTorrentListReceived(callID, list, null); } if (AndroidUtils.DEBUG_RPC) { Log.d(TAG, id + "] rpcError. fake listener for " + listReceivedListeners.length + "/" + (l == null ? 0 : 1) + ", " + list); } } }); } private void sendRequest(final String id, final Map data, final ReplyMapReceivedListener l) { if (id == null || data == null) { if (AndroidUtils.DEBUG_RPC) { Log.e(TAG, "sendRequest(" + id + "," + JSONUtils.encodeToJSON(data) + "," + l + ")"); } return; } new Thread(new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { data.put("random", Integer.toHexString(cacheBuster++)); try { Map reply = RestJsonClient.connect(id, rpcURL, data, headers, creds, supportsGZIP); String result = MapUtils.getMapString(reply, "result", ""); if (l != null) { if (result.equals("success")) { l.rpcSuccess(id, MapUtils.getMapMap(reply, "arguments", Collections.EMPTY_MAP)); } else { if (AndroidUtils.DEBUG_RPC) { Log.d(TAG, id + "] rpcFailure: " + result); } // clean up things like: // org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderException: http://foo.torrent: I/O Exception while downloading 'http://foo.torrent', Operation timed out result = result.replaceAll("org\\.[a-z.]+:", ""); result = result.replaceAll("com\\.[a-z.]+:", ""); l.rpcFailure(id, result); } } } catch (RPCException e) { HttpResponse httpResponse = e.getHttpResponse(); if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == 409) { if (AndroidUtils.DEBUG_RPC) { Log.d(TAG, "409: retrying"); } Header header = httpResponse.getFirstHeader("X-Transmission-Session-Id"); headers = new Header[] { header }; sendRequest(id, data, l); return; } Throwable cause = e.getCause(); if (sessionInfo != null && (cause instanceof HttpHostConnectException)) { RemoteProfile remoteProfile = sessionInfo.getRemoteProfile(); if (remoteProfile != null && remoteProfile.getRemoteType() == RemoteProfile.TYPE_CORE) { VuzeRemoteApp.waitForCore(sessionInfo.getCurrentActivity(), 10000); sendRequest(id, data, l); return; } } if (l != null) { l.rpcError(id, e); } // TODO: trigger a generic error listener, so we can put a "Could not connect" status text somewhere } } }, "sendRequest" + id).start(); } public synchronized List<String> getBasicTorrentFieldIDs() { if (basicTorrentFieldIDs == null) { basicTorrentFieldIDs = new ArrayList<>(); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_ID); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_HASH_STRING); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_NAME); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_PERCENT_DONE); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_SIZE_WHEN_DONE); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_RATE_UPLOAD); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_RATE_DOWNLOAD); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_ERROR); // TransmissionVars.TR_STAT_* basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_ERROR_STRING); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_ETA); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_POSITION); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_UPLOAD_RATIO); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_DATE_ADDED); basicTorrentFieldIDs.add("speedHistory"); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_LEFT_UNTIL_DONE); basicTorrentFieldIDs.add("tag-uids"); basicTorrentFieldIDs.add(TransmissionVars.FIELD_TORRENT_STATUS); // TransmissionVars.TR_STATUS_* } List<String> fields = new ArrayList<>(basicTorrentFieldIDs); if (hasFileCountField == null) { fields.add(TransmissionVars.FIELD_TORRENT_FILE_COUNT); // azRPC 2+ fields.add(TransmissionVars.FIELD_TORRENT_PRIORITIES); // for filesCount } else if (hasFileCountField) { fields.add(TransmissionVars.FIELD_TORRENT_FILE_COUNT); // azRPC 2+ } else { fields.add(TransmissionVars.FIELD_TORRENT_PRIORITIES); // for filesCount } return fields; } /** * Get recently-active torrents, or all torrents if there are no recents */ public void getRecentTorrents(String callID, final TorrentListReceivedListener l) { getTorrents(callID, "recently-active", getBasicTorrentFieldIDs(), null, null, new TorrentListReceivedListener() { boolean doingAll = false; @Override public void rpcTorrentListReceived(String callID, List<?> addedTorrentMaps, List<?> removedTorrentIDs) { long diff = System.currentTimeMillis() - lastRecentTorrentGet; if (!doingAll && addedTorrentMaps.size() == 0) { if (diff >= RECENTLY_ACTIVE_MS) { doingAll = true; getAllTorrents(callID, this); } } else { lastRecentTorrentGet = System.currentTimeMillis(); } if (l != null) { l.rpcTorrentListReceived(callID, addedTorrentMaps, removedTorrentIDs); } } }); } private List<String> getFileInfoFields() { List<String> fieldIDs = getBasicTorrentFieldIDs(); fieldIDs.add(TransmissionVars.FIELD_TORRENT_FILES); fieldIDs.add(TransmissionVars.FIELD_TORRENT_FILESTATS); return fieldIDs; } public void getTorrentFileInfo(String callID, Object ids, int[] fileIndexes, TorrentListReceivedListener l) { getTorrents(callID, ids, getFileInfoFields(), fileIndexes, defaultFileFields, l); } public void getTorrentPeerInfo(String callID, Object ids, TorrentListReceivedListener l) { List<String> fieldIDs = new ArrayList<>(); fieldIDs.add(TransmissionVars.FIELD_TORRENT_ID); fieldIDs.add("peers"); getTorrents(callID, ids, fieldIDs, null, null, l); } public void simpleRpcCall(String method, ReplyMapReceivedListener l) { simpleRpcCall(method, (Map) null, l); } public void simpleRpcCall(String method, long[] ids, ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", method); if (ids != null) { Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", ids); } sendRequest(method, map, l); } public void simpleRpcCall(String method, Map arguments, ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", method); if (arguments != null) { map.put("arguments", arguments); } sendRequest(method, map, l); } public void simpleRpcCallWithRefresh(String callID, String method, long[] ids, ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", method); if (ids != null) { Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", ids); } sendRequest(method, map, new ReplyMapReceivedListenerWithRefresh(callID, l, ids)); } public void startTorrents(String callID, long[] ids, boolean forceStart, ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", forceStart ? "torrent-start-now" : "torrent-start"); if (ids != null) { Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", ids); } sendRequest("startTorrents", map, new ReplyMapReceivedListenerWithRefresh(callID, l, ids)); } public void stopTorrents(String callID, long[] ids, final ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-stop"); if (ids != null) { Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", ids); } sendRequest("stopTorrents", map, new ReplyMapReceivedListenerWithRefresh(callID, l, ids)); } public void setFilePriority(String callID, long torrentID, int[] fileIndexes, int priority, final ReplyMapReceivedListener l) { long[] ids = { torrentID }; Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-set"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", ids); String key; switch (priority) { case TransmissionVars.TR_PRI_HIGH: key = "priority-high"; break; case TransmissionVars.TR_PRI_NORMAL: key = "priority-normal"; break; case TransmissionVars.TR_PRI_LOW: key = "priority-low"; break; default: return; } mapArguments.put(key, fileIndexes); sendRequest("setFilePriority", map, new ReplyMapReceivedListenerWithRefresh(callID, l, ids, fileIndexes, null)); } public void setWantState(String callID, long torrentID, int[] fileIndexes, boolean wanted, final ReplyMapReceivedListener l) { long[] torrentIDs = { torrentID }; Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-set"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", torrentIDs); mapArguments.put(wanted ? "files-wanted" : "files-unwanted", fileIndexes); sendRequest("setWantState", map, new ReplyMapReceivedListenerWithRefresh(callID, l, torrentIDs, fileIndexes, null)); } public void setDisplayName(String callID, long torrentID, String newName) { long[] torrentIDs = { torrentID }; Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-set"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", torrentIDs); mapArguments.put("name", newName); sendRequest("setDisplayName", map, new ReplyMapReceivedListenerWithRefresh(callID, null, torrentIDs)); } public void addTagToTorrents(String callID, long[] torrentIDs, final Object[] tags) { if (tags == null || tags.length == 0) { return; } Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-set"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", torrentIDs); mapArguments.put("tagAdd", tags); sendRequest("addTagToTorrent", map, new ReplyMapReceivedListenerWithRefresh(callID, null, torrentIDs) { @Override public void rpcSuccess(String id, Map optionalMap) { if (tags[0] instanceof String) { sessionInfo.refreshTags(); } super.rpcSuccess(id, optionalMap); } }); } public void removeTagFromTorrents(String callID, long[] torrentIDs, Object[] tags) { Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-set"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); if (rpcVersionAZ < 4) { // Older AZ RPC only allowed removal of tag names for (int i = 0; i < tags.length; i++) { if (tags[i] instanceof Number) { Map<?, ?> tag = sessionInfo.getTag(((Number) tags[i]).longValue()); tags[i] = MapUtils.getMapString(tag, "name", null); } } } mapArguments.put("ids", torrentIDs); mapArguments.put("tagRemove", tags); sendRequest("removeTagFromTorrent", map, new ReplyMapReceivedListenerWithRefresh(callID, null, torrentIDs)); } /** * To ensure session torrent list is fully up to date, * you should be using {@link SessionInfo#addTorrentListReceivedListener} * instead of this one. */ public void addTorrentListReceivedListener(TorrentListReceivedListener l) { synchronized (torrentListReceivedListeners) { if (!torrentListReceivedListeners.contains(l)) { torrentListReceivedListeners.add(l); } } } public void removeTorrentListReceivedListener(TorrentListReceivedListener l) { synchronized (torrentListReceivedListeners) { torrentListReceivedListeners.remove(l); } } public void addSessionSettingsReceivedListener(SessionSettingsReceivedListener l) { synchronized (sessionSettingsReceivedListeners) { if (!sessionSettingsReceivedListeners.contains(l)) { sessionSettingsReceivedListeners.add(l); if (latestSessionSettings != null) { l.sessionPropertiesUpdated(latestSessionSettings); } } } } public void removeSessionSettingsReceivedListener(SessionSettingsReceivedListener l) { synchronized (sessionSettingsReceivedListeners) { sessionSettingsReceivedListeners.remove(l); } } public TorrentListReceivedListener[] getTorrentListReceivedListeners() { return torrentListReceivedListeners .toArray(new TorrentListReceivedListener[torrentListReceivedListeners.size()]); } public void moveTorrent(long id, String newLocation, ReplyMapReceivedListener listener) { Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-set-location"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); long[] ids = new long[] { id }; mapArguments.put("ids", ids); mapArguments.put("move", true); mapArguments.put("location", newLocation); sendRequest("torrent-set-location", map, new ReplyMapReceivedListenerWithRefresh(TAG, listener, ids)); } public void removeTorrent(long[] ids, boolean deleteData, final ReplyMapReceivedListener listener) { Map<String, Object> map = new HashMap<>(); map.put("method", "torrent-remove"); Map<String, Object> mapArguments = new HashMap<>(); map.put("arguments", mapArguments); mapArguments.put("ids", ids); mapArguments.put("delete-local-data", deleteData); sendRequest("torrent-remove", map, new ReplyMapReceivedListener() { @Override public void rpcSuccess(String id, Map<?, ?> optionalMap) { try { Thread.sleep(500); } catch (InterruptedException e) { } getRecentTorrents(id, null); if (listener != null) { listener.rpcSuccess(id, optionalMap); } } @Override public void rpcFailure(String id, String message) { if (listener != null) { listener.rpcFailure(id, message); } } @Override public void rpcError(String id, Exception e) { if (listener != null) { listener.rpcError(id, e); } } }); } public void updateSettings(Map<String, Object> changes) { Map<String, Object> map = new HashMap<>(); map.put("method", "session-set"); map.put("arguments", changes); sendRequest("session-set", map, null); } /** * Listener's map will have a "size-bytes" key */ public void getFreeSpace(String path, ReplyMapReceivedListener l) { Map<String, Object> map = new HashMap<>(); map.put("path", path); simpleRpcCall("free-space", map, l); } public int getRPCVersion() { return rpcVersion; } public int getRPCVersionAZ() { return rpcVersionAZ; } public boolean getSupportsRCM() { return supportsRCM; } public boolean getSupportsTorrentRename() { return supportsTorrentRename; } public boolean getSupportsTags() { return supportsTags; } public void setDefaultFileFields(String[] fileFields) { this.defaultFileFields = fileFields; } }