Eric Kok
12 years ago
5 changed files with 498 additions and 5 deletions
@ -0,0 +1,454 @@
@@ -0,0 +1,454 @@
|
||||
/* |
||||
* 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.Synology; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLEncoder; |
||||
import java.util.ArrayList; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
|
||||
import org.apache.http.HttpEntity; |
||||
import org.apache.http.HttpResponse; |
||||
import org.apache.http.client.methods.HttpGet; |
||||
import org.apache.http.impl.client.DefaultHttpClient; |
||||
import org.json.JSONArray; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
import org.transdroid.daemon.Daemon; |
||||
import org.transdroid.daemon.DaemonException; |
||||
import org.transdroid.daemon.DaemonException.ExceptionType; |
||||
import org.transdroid.daemon.DaemonSettings; |
||||
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.TorrentStatus; |
||||
import org.transdroid.daemon.task.AddByMagnetUrlTask; |
||||
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.GetFileListTask; |
||||
import org.transdroid.daemon.task.GetFileListTaskSuccessResult; |
||||
import org.transdroid.daemon.task.GetTorrentDetailsTask; |
||||
import org.transdroid.daemon.task.GetTorrentDetailsTaskSuccessResult; |
||||
import org.transdroid.daemon.task.RetrieveTask; |
||||
import org.transdroid.daemon.task.RetrieveTaskSuccessResult; |
||||
import org.transdroid.daemon.task.SetTransferRatesTask; |
||||
import org.transdroid.daemon.util.Collections2; |
||||
import org.transdroid.daemon.util.DLog; |
||||
import org.transdroid.daemon.util.HttpHelper; |
||||
|
||||
/** |
||||
* The daemon adapter from the Synology Download Station torrent client. |
||||
* |
||||
*/ |
||||
public class SynologyAdapter implements IDaemonAdapter { |
||||
|
||||
private static final String LOG_NAME = "Synology daemon"; |
||||
|
||||
private DaemonSettings settings; |
||||
private DefaultHttpClient httpClient; |
||||
|
||||
private String sid; |
||||
|
||||
public SynologyAdapter(DaemonSettings settings) { |
||||
this.settings = settings; |
||||
} |
||||
|
||||
@Override |
||||
public DaemonTaskResult executeTask(DaemonTask task) { |
||||
String tid; |
||||
try { |
||||
switch (task.getMethod()) { |
||||
case Retrieve: |
||||
return new RetrieveTaskSuccessResult((RetrieveTask) task, tasksList(), null); |
||||
case GetStats: |
||||
return null; |
||||
case GetTorrentDetails: |
||||
tid = task.getTargetTorrent().getUniqueID(); |
||||
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, torrentDetails(tid)); |
||||
case GetFileList: |
||||
tid = task.getTargetTorrent().getUniqueID(); |
||||
return new GetFileListTaskSuccessResult((GetFileListTask) task, fileList(tid)); |
||||
case AddByFile: |
||||
return null; |
||||
case AddByUrl: |
||||
String url = ((AddByUrlTask)task).getUrl(); |
||||
createTask(url); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case AddByMagnetUrl: |
||||
String magnet = ((AddByMagnetUrlTask)task).getUrl(); |
||||
createTask(magnet); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case Remove: |
||||
tid = task.getTargetTorrent().getUniqueID(); |
||||
removeTask(tid); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case Pause: |
||||
tid = task.getTargetTorrent().getUniqueID(); |
||||
pauseTask(tid); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case PauseAll: |
||||
pauseAllTasks(); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case Resume: |
||||
tid = task.getTargetTorrent().getUniqueID(); |
||||
resumeTask(tid); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case ResumeAll: |
||||
resumeAllTasks(); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case SetDownloadLocation: |
||||
return null; |
||||
case SetFilePriorities: |
||||
return null; |
||||
case SetTransferRates: |
||||
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; |
||||
int uploadRate = ratesTask.getUploadRate() == null ? 0 : ratesTask.getUploadRate().intValue(); |
||||
int downloadRate = ratesTask.getDownloadRate() == null ? 0 : ratesTask.getDownloadRate().intValue(); |
||||
setTransferRates(uploadRate, downloadRate); |
||||
return new DaemonTaskSuccessResult(task); |
||||
case SetAlternativeMode: |
||||
default: |
||||
return null; |
||||
} |
||||
} catch (DaemonException e) { |
||||
return new DaemonTaskFailureResult(task, e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Daemon getType() { |
||||
return settings.getType(); |
||||
} |
||||
|
||||
@Override |
||||
public DaemonSettings getSettings() { |
||||
return this.settings; |
||||
} |
||||
|
||||
// Synology API
|
||||
|
||||
private String login() throws DaemonException { |
||||
DLog.d(LOG_NAME, "login()"); |
||||
try { |
||||
return new SynoRequest( |
||||
"auth.cgi", |
||||
"SYNO.API.Auth", |
||||
"2" |
||||
).get("&method=login&account=" + settings.getUsername() + "&passwd=" + settings.getPassword() + "&session=DownloadStation&format=sid" |
||||
).getData().getString("sid"); |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); |
||||
} |
||||
} |
||||
|
||||
private void setTransferRates(int uploadRate, int downloadRate) throws DaemonException { |
||||
authGet("SYNO.DownloadStation.Info", "1", "DownloadStation/info.cgi", |
||||
"&method=setserverconfig&bt_max_upload=" + uploadRate + "&bt_max_download=" + downloadRate).ensureSuccess(); |
||||
} |
||||
|
||||
private void createTask(String uri) throws DaemonException { |
||||
try { |
||||
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=create&uri=" + URLEncoder.encode(uri, "UTF-8")).ensureSuccess(); |
||||
} catch (UnsupportedEncodingException e) { |
||||
// Never happens
|
||||
throw new DaemonException(ExceptionType.UnexpectedResponse, e.toString()); |
||||
} |
||||
} |
||||
|
||||
private void removeTask(String tid) throws DaemonException { |
||||
List<String> tids = new ArrayList<String>(); |
||||
tids.add(tid); |
||||
removeTasks(tids); |
||||
} |
||||
|
||||
private void pauseTask(String tid) throws DaemonException { |
||||
List<String> tids = new ArrayList<String>(); |
||||
tids.add(tid); |
||||
pauseTasks(tids); |
||||
} |
||||
|
||||
private void resumeTask(String tid) throws DaemonException { |
||||
List<String> tids = new ArrayList<String>(); |
||||
tids.add(tid); |
||||
resumeTasks(tids); |
||||
} |
||||
|
||||
private void pauseAllTasks() throws DaemonException { |
||||
List<String> tids = new ArrayList<String>(); |
||||
for (Torrent torrent: tasksList()) { |
||||
tids.add(torrent.getUniqueID()); |
||||
} |
||||
pauseTasks(tids); |
||||
} |
||||
|
||||
private void resumeAllTasks() throws DaemonException { |
||||
List<String> tids = new ArrayList<String>(); |
||||
for (Torrent torrent: tasksList()) { |
||||
tids.add(torrent.getUniqueID()); |
||||
} |
||||
resumeTasks(tids); |
||||
} |
||||
|
||||
private void removeTasks(List<String> tids) throws DaemonException { |
||||
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=delete&id=" + Collections2.joinString(tids, ",") + "").ensureSuccess(); |
||||
} |
||||
|
||||
private void pauseTasks(List<String> tids) throws DaemonException { |
||||
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=pause&id=" + Collections2.joinString(tids, ",")).ensureSuccess(); |
||||
} |
||||
|
||||
private void resumeTasks(List<String> tids) throws DaemonException { |
||||
authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=resume&id=" + Collections2.joinString(tids, ",")).ensureSuccess(); |
||||
} |
||||
|
||||
private List<Torrent> tasksList() throws DaemonException { |
||||
try { |
||||
JSONArray jsonTasks = authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=list&additional=detail,transfer,tracker").getData().getJSONArray("tasks"); |
||||
DLog.d(LOG_NAME, "Tasks = " + jsonTasks.toString()); |
||||
List<Torrent> result = new ArrayList<Torrent>(); |
||||
for (int i = 0; i < jsonTasks.length(); i++) { |
||||
result.add(parseTorrent(i, jsonTasks.getJSONObject(i))); |
||||
} |
||||
return result; |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); |
||||
} |
||||
} |
||||
|
||||
private List<TorrentFile> fileList(String torrentId) throws DaemonException { |
||||
try { |
||||
List<TorrentFile> result = new ArrayList<TorrentFile>(); |
||||
JSONObject jsonTask = authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=getinfo&id=" + torrentId + "&additional=detail,transfer,tracker,file").getData().getJSONArray("tasks").getJSONObject(0); |
||||
DLog.d(LOG_NAME, "File list = " + jsonTask.toString()); |
||||
JSONObject additional = jsonTask.getJSONObject("additional"); |
||||
if (!additional.has("file")) return result; |
||||
JSONArray files = additional.getJSONArray("file"); |
||||
for (int i = 0; i < files.length(); i++) { |
||||
JSONObject task = files.getJSONObject(i); |
||||
result.add(new TorrentFile( |
||||
task.getString("filename"), |
||||
task.getString("filename"), |
||||
null, |
||||
null, |
||||
task.getLong("size"), |
||||
task.getLong("size_downloaded"), |
||||
priority(task.getString("priority")) |
||||
)); |
||||
} |
||||
return result; |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); |
||||
} |
||||
} |
||||
|
||||
private TorrentDetails torrentDetails(String torrentId) throws DaemonException { |
||||
List<String> trackers = new ArrayList<String>(); |
||||
List<String> errors = new ArrayList<String>(); |
||||
try { |
||||
JSONObject jsonTorrent = authGet("SYNO.DownloadStation.Task", "1", "DownloadStation/task.cgi", "&method=getinfo&id=" + torrentId + "&additional=tracker").getData().getJSONArray("tasks").getJSONObject(0); |
||||
JSONObject additional = jsonTorrent.getJSONObject("additional"); |
||||
if (additional.has("tracker")) { |
||||
JSONArray tracker = additional.getJSONArray("tracker"); |
||||
for (int i = 0; i < tracker.length(); i++) { |
||||
JSONObject t = tracker.getJSONObject(i); |
||||
if ("Success".equals(t.getString("status"))) { |
||||
trackers.add(t.getString("url")); |
||||
} else { |
||||
errors.add(t.getString("status")); |
||||
} |
||||
} |
||||
} |
||||
return new TorrentDetails(trackers, errors); |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); |
||||
} |
||||
} |
||||
|
||||
private Torrent parseTorrent(long id, JSONObject jsonTorrent) throws JSONException, DaemonException { |
||||
JSONObject additional = jsonTorrent.getJSONObject("additional"); |
||||
JSONObject detail = additional.getJSONObject("detail"); |
||||
JSONObject transfer = additional.getJSONObject("transfer"); |
||||
long downloaded = transfer.getLong("size_downloaded"); |
||||
int speed = transfer.getInt("speed_download"); |
||||
long size = jsonTorrent.getLong("size"); |
||||
Float eta = new Float(size - downloaded) / speed; |
||||
int totalPeers = 0; |
||||
if (additional.has("tracker")) { |
||||
JSONArray tracker = additional.getJSONArray("tracker"); |
||||
for (int i = 0; i < tracker.length(); i++) { |
||||
JSONObject t = tracker.getJSONObject(i); |
||||
if ("Success".equals(t.getString("status"))) { |
||||
totalPeers += t.getInt("peers"); |
||||
totalPeers += t.getInt("seeds"); |
||||
} |
||||
} |
||||
} |
||||
return new Torrent( |
||||
id, |
||||
jsonTorrent.getString("id"), |
||||
jsonTorrent.getString("title"), |
||||
torrentStatus(jsonTorrent.getString("status")), |
||||
detail.getString("destination"), |
||||
speed, |
||||
transfer.getInt("speed_upload"), |
||||
detail.getInt("connected_leechers"), |
||||
detail.getInt("connected_seeders"), |
||||
totalPeers, |
||||
totalPeers, |
||||
eta.intValue(), |
||||
downloaded, |
||||
Integer.parseInt(transfer.getString("size_uploaded")), |
||||
size, |
||||
(size == 0) ? 0 : (new Float(downloaded) / size), |
||||
0, |
||||
jsonTorrent.getString("title"), |
||||
new Date(detail.getLong("create_time") * 1000), |
||||
null, |
||||
"" |
||||
); |
||||
} |
||||
|
||||
private TorrentStatus torrentStatus(String status) { |
||||
if ("downloading".equals(status)) return TorrentStatus.Downloading; |
||||
if ("seeding".equals(status)) return TorrentStatus.Seeding; |
||||
if ("finished".equals(status)) return TorrentStatus.Paused; |
||||
if ("finishing".equals(status)) return TorrentStatus.Paused; |
||||
if ("waiting".equals(status)) return TorrentStatus.Waiting; |
||||
if ("paused".equals(status)) return TorrentStatus.Paused; |
||||
if ("error".equals(status)) return TorrentStatus.Error; |
||||
return TorrentStatus.Unknown; |
||||
} |
||||
|
||||
private Priority priority(String priority) { |
||||
if ("low".equals(priority)) return Priority.Low; |
||||
if ("normal".equals(priority)) return Priority.Normal; |
||||
if ("high".equals(priority)) return Priority.High; |
||||
return Priority.Off; |
||||
} |
||||
|
||||
/** |
||||
* Authenticated GET. If no session open, a login authGet will be done before-hand. |
||||
*/ |
||||
private SynoResponse authGet(String api, String version, String path, String params) throws DaemonException { |
||||
if (sid == null) { |
||||
sid = login(); |
||||
} |
||||
return new SynoRequest(path, api, version).get(params + "&_sid=" + sid); |
||||
} |
||||
|
||||
private DefaultHttpClient getHttpClient() throws DaemonException { |
||||
if (httpClient == null) |
||||
httpClient = HttpHelper.createStandardHttpClient(settings, true); |
||||
return httpClient; |
||||
} |
||||
|
||||
private class SynoRequest { |
||||
private final String path; |
||||
private final String api; |
||||
private final String version; |
||||
|
||||
public SynoRequest(String path, String api, String version) { |
||||
this.path = path; |
||||
this.api = api; |
||||
this.version = version; |
||||
} |
||||
|
||||
public SynoResponse get(String params) throws DaemonException { |
||||
try { |
||||
return new SynoResponse(getHttpClient().execute(new HttpGet(buildURL(params)))); |
||||
} catch (IOException e) { |
||||
throw new DaemonException(ExceptionType.ConnectionError, e.toString()); |
||||
} |
||||
} |
||||
|
||||
private String buildURL(String params) { |
||||
return (settings.getSsl() ? "https://" : "http://") |
||||
+ settings.getAddress() |
||||
+ ":" + settings.getPort() |
||||
+ "/webapi/" + path |
||||
+ "?api=" + api |
||||
+ "&version=" + version |
||||
+ params; |
||||
} |
||||
|
||||
} |
||||
|
||||
private static class SynoResponse { |
||||
|
||||
private final HttpResponse response; |
||||
|
||||
public SynoResponse(HttpResponse response) { |
||||
this.response = response; |
||||
} |
||||
|
||||
public JSONObject getData() throws DaemonException { |
||||
JSONObject json = getJson(); |
||||
try { |
||||
if (json.getBoolean("success")) { |
||||
return json.getJSONObject("data"); |
||||
} else { |
||||
DLog.e(LOG_NAME, "not a success: " + json.toString()); |
||||
throw new DaemonException(ExceptionType.AuthenticationFailure, json.getString("error")); |
||||
} |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); |
||||
} |
||||
} |
||||
|
||||
public JSONObject getJson() throws DaemonException { |
||||
try { |
||||
HttpEntity entity = response.getEntity(); |
||||
if (entity == null) { |
||||
DLog.e(LOG_NAME, "Error: No entity in HTTP response"); |
||||
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response."); |
||||
} |
||||
// Read JSON response
|
||||
java.io.InputStream instream = entity.getContent(); |
||||
String result = HttpHelper.ConvertStreamToString(instream); |
||||
JSONObject json; |
||||
json = new JSONObject(result); |
||||
instream.close(); |
||||
return json; |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.UnexpectedResponse, "Bad JSON"); |
||||
} catch (IOException e) { |
||||
DLog.e(LOG_NAME, "getJson error: " + e.toString()); |
||||
throw new DaemonException(ExceptionType.AuthenticationFailure, e.toString()); |
||||
} |
||||
} |
||||
|
||||
public void ensureSuccess() throws DaemonException { |
||||
JSONObject json = getJson(); |
||||
try { |
||||
if (!json.getBoolean("success")) |
||||
throw new DaemonException(ExceptionType.UnexpectedResponse, json.getString("error")); |
||||
} catch (JSONException e) { |
||||
throw new DaemonException(ExceptionType.ParsingFailed, e.toString()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
package org.transdroid.daemon.util; |
||||
|
||||
import java.util.Iterator; |
||||
|
||||
/** |
||||
* Helpers on Collections |
||||
*/ |
||||
public class Collections2 { |
||||
|
||||
/** |
||||
* Create a String from an iterable with a separator. Exemple: mkString({1,2,3,4}, ":" => "1:2:3:4" |
||||
*/ |
||||
public static <T> String joinString(Iterable<T> iterable, String separator) { |
||||
boolean first = true; |
||||
String result = ""; |
||||
Iterator<T> it = iterable.iterator(); |
||||
while (it.hasNext()) { |
||||
result = (first ? "" : separator) + it.next().toString(); |
||||
first = false; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue