/* * This file is part of Transdroid <http://www.transdroid.org> * * 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.daemon.Tfb4rt; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.net.URI; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.transdroid.core.gui.log.Log; import org.transdroid.daemon.Daemon; import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.Torrent; import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.DaemonTask; import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskSuccessResult; import org.transdroid.daemon.task.RemoveTask; import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.util.HttpHelper; import com.android.internalcopy.http.multipart.FilePart; import com.android.internalcopy.http.multipart.MultipartEntity; import com.android.internalcopy.http.multipart.Part; /** * An adapter that allows for easy access to Torrentflux-b4rt installs. Communication is handled via HTTP GET requests * and XML responses. * @author erickok */ public class Tfb4rtAdapter implements IDaemonAdapter { private static final String LOG_NAME = "Torrentflux-b4rt daemon"; private static final String RPC_URL_STATS = "/stats.php?t=all&f=xml"; private static final String RPC_URL_DISPATCH = "/dispatcher.php?action="; private static final String RPC_URL_DISPATCH2 = "&riid=_exit_"; private static final String RPC_URL_TRANSFER = "&transfer="; private static final String RPC_URL_AID = "&aid=2"; private static final String RPC_URL_URL = "&url="; private static final String RPC_URL_USER = "&username="; private static final String RPC_URL_PASS = "&md5pass="; private DaemonSettings settings; private DefaultHttpClient httpclient; /** * Initialises an adapter that provides operations to the Torrentflux-b4rt pages */ public Tfb4rtAdapter(DaemonSettings settings) { this.settings = settings; } @Override public DaemonTaskResult executeTask(Log log, DaemonTask task) { try { switch (task.getMethod()) { case Retrieve: // Request all torrents from server return new RetrieveTaskSuccessResult((RetrieveTask) task, makeStatsRequest(log), null); case AddByFile: // Add a torrent to the server by sending the contents of a local .torrent file String file = ((AddByFileTask) task).getFile(); makeFileUploadRequest(log, "fileUpload", file); return null; case AddByUrl: // Request to add a torrent by URL String url = ((AddByUrlTask) task).getUrl(); makeActionRequest(log, "urlUpload", url); return new DaemonTaskSuccessResult(task); case Remove: // Remove a torrent RemoveTask removeTask = (RemoveTask) task; makeActionRequest(log, (removeTask.includingData() ? "deleteWithData" : "delete"), task.getTargetTorrent() .getUniqueID()); return new DaemonTaskSuccessResult(task); case Pause: // Pause a torrent makeActionRequest(log, "stop", task.getTargetTorrent().getUniqueID()); return new DaemonTaskSuccessResult(task); case PauseAll: // Pause all torrents makeActionRequest(log, "bulkStop", null); return new DaemonTaskSuccessResult(task); case Resume: // Resume a torrent makeActionRequest(log, "start", task.getTargetTorrent().getUniqueID()); return new DaemonTaskSuccessResult(task); case ResumeAll: // Resume all torrents makeActionRequest(log, "bulkStart", null); return new DaemonTaskSuccessResult(task); case SetTransferRates: // Request to set the maximum transfer rates // TODO: Implement this? return null; default: return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType())); } } catch (DaemonException e) { return new DaemonTaskFailureResult(task, e); } } private List<Torrent> makeStatsRequest(Log log) throws DaemonException { try { // Initialise the HTTP client if (httpclient == null) { initialise(); } // Make request HttpGet httpget = new HttpGet(buildWebUIUrl(RPC_URL_STATS)); HttpResponse response = httpclient.execute(httpget); // Read XML response InputStream instream = response.getEntity().getContent(); List<Torrent> torrents = StatsParser.parse(new InputStreamReader(instream)); instream.close(); return torrents; } catch (DaemonException e) { log.d(LOG_NAME, "Parsing error: " + e.toString()); throw e; } catch (Exception e) { log.d(LOG_NAME, "Error: " + e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString()); } } private boolean makeActionRequest(Log log, String action, String target) throws DaemonException { try { // Initialise the HTTP client if (httpclient == null) { initialise(); } // Make request HttpGet httpget = new HttpGet(buildWebUIUrl(RPC_URL_DISPATCH + action + RPC_URL_DISPATCH2 + RPC_URL_AID + (action.equals("urlUpload") ? RPC_URL_URL : RPC_URL_TRANSFER) + target)); HttpResponse response = httpclient.execute(httpget); // Read response (a successful action always returned '1') InputStream instream = response.getEntity().getContent(); String result = HttpHelper.convertStreamToString(instream); instream.close(); if (result.trim().equals("1")) { return true; } else { throw new DaemonException(ExceptionType.UnexpectedResponse, "Action response was not 1 but " + result); } } catch (DaemonException e) { log.d(LOG_NAME, action + " request error: " + e.toString()); throw e; } catch (Exception e) { log.d(LOG_NAME, "Error: " + e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString()); } } private boolean makeFileUploadRequest(Log log, String action, String target) throws DaemonException { try { // Initialise the HTTP client if (httpclient == null) { initialise(); } // Make request HttpPost httppost = new HttpPost(buildWebUIUrl(RPC_URL_DISPATCH + action + RPC_URL_DISPATCH2 + RPC_URL_AID)); File upload = new File(URI.create(target)); Part[] parts = { new FilePart("upload_files[]", upload) }; httppost.setEntity(new MultipartEntity(parts, httppost.getParams())); HttpResponse response = httpclient.execute(httppost); // Read response (a successful action always returned '1') InputStream instream = response.getEntity().getContent(); String result = HttpHelper.convertStreamToString(instream); instream.close(); if (result.equals("1")) { return true; } else { throw new DaemonException(ExceptionType.UnexpectedResponse, "Action response was not 1 but " + result); } } catch (DaemonException e) { log.d(LOG_NAME, action + " request error: " + e.toString()); throw e; } catch (Exception e) { log.d(LOG_NAME, "Error: " + e.toString()); throw new DaemonException(ExceptionType.ConnectionError, e.toString()); } } /** * Instantiates an HTTP client that can be used for all Torrentflux-b4rt requests. * @throws DaemonException On conflicting or missing settings */ private void initialise() throws DaemonException { httpclient = HttpHelper.createStandardHttpClient(settings, true); } /** * Build the URL of specific Torrentflux site request from the user settings and some requested action. * @param act The action to perform, which is an already build query string without usernmae/password, i.e. * dispatcher.php?action=stop&transfer=ubuntu.torrent * @return The URL of a specific request, i.e. * http://localhost:80/turrentflux/dispatcher.php?action=stop&transfer=ubuntu * .torrent&username=admin&md5pass=asd98as7d */ private String buildWebUIUrl(String act) { String folder = ""; if (settings.getFolder() != null) { folder = settings.getFolder(); if (folder.endsWith("/")) folder = folder.substring(0, folder.length() - 1); } return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + folder + act + RPC_URL_USER + settings.getUsername() + RPC_URL_PASS + md5Pass((settings.getPassword() == null ? "" : settings.getPassword())); } /** * Calculate the MD5 hash of a password to use with the Torrentflux-b4rt dispatcher requests. * @param pass The plain text password * @return A hex-formatted MD5-hashed string of the password */ public static String md5Pass(String pass) { try { MessageDigest m = MessageDigest.getInstance("MD5"); byte[] data = pass.getBytes(); m.update(data, 0, data.length); BigInteger i = new BigInteger(1, m.digest()); return String.format("%1$032X", i); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } @Override public Daemon getType() { return settings.getType(); } @Override public DaemonSettings getSettings() { return this.settings; } }