package com.openseedbox.backend.node;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.openseedbox.backend.IFile;
import com.openseedbox.backend.IPeer;
import com.openseedbox.backend.ISessionStatistics;
import com.openseedbox.backend.ITorrent;
import com.openseedbox.backend.ITorrentBackend;
import com.openseedbox.backend.ITracker;
import com.openseedbox.backend.TorrentState;
import com.openseedbox.code.MessageException;
import com.openseedbox.code.Util;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.openseedbox.models.Node;
import com.openseedbox.models.Torrent;
import play.libs.WS.FileParam;
import play.libs.WS.HttpResponse;
import play.libs.WS.WSRequest;
/**
* Essentially this is a "pass-thru" torrent backend that delegates
* directly to the openseedbox-server API (for whatever node this class
* was initialised with).
* The openseedbox-server API then uses this same interface to interact with
* whatever backend its using
* @author Erin Drummond
*/
public class NodeBackend implements ITorrentBackend {
private Node node;
public NodeBackend(Node node) {
this.node = node;
}
public boolean isInstalled() {
return node.getNodeStatus().isBackendInstalled();
}
public String getName() {
return "openseedbox-node";
}
public String getVersion() {
return "0.1";
}
public void start() {
HttpResponse res = node.getWebService("/backend/start").get();
getResponseBodyOrError(res);
}
public void stop() {
HttpResponse res = node.getWebService("/backend/stop").get();
getResponseBodyOrError(res);
}
public void restart() {
HttpResponse res = node.getWebService("/backend/restart").get();
getResponseBodyOrError(res);
}
public void cleanup() {
HttpResponse res = node.getWebService("/backend/cleanup").get();
getResponseBodyOrError(res);
}
public boolean isRunning() {
return node.getNodeStatus().isBackendRunning();
}
public ITorrent addTorrent(File file) {
return add(file, null);
}
public ITorrent addTorrent(String urlOrMagnet) {
return add(null, urlOrMagnet);
}
private ITorrent add(File file, String urlOrMagnet) {
WSRequest req = node.getWebService("/torrents/add");
req.timeout("1min"); //some torrents can take ages to add due to the encryption and having to be allocated
HttpResponse res;
if (file != null) {
FileParam fp = new FileParam(file, "torrent");
req.files(fp);
res = req.post();
} else {
req.setParameter("url", urlOrMagnet);
res = req.get();
}
JsonObject ob = getResponseBodyOrError(res).getAsJsonObject();
return Util.getGson().fromJson(ob.get("torrent"), NodeTorrent.class);
}
public void removeTorrent(String hash) {
WSRequest req = node.getWebService("/torrents/remove", hash);
getResponseBodyOrError(req.get());
}
public void removeTorrent(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/remove", hashes);
getResponseBodyOrError(req.get());
}
public void startTorrent(String hash) {
WSRequest req = node.getWebService("/torrents/start", hash);
getResponseBodyOrError(req.get());
ITorrent status = getTorrentStatus(hash);
if (status.isSeeding()) {
updateDatabaseTorrent(hash, TorrentState.SEEDING);
} else if (status.isMetadataDownloading()) {
updateDatabaseTorrent(hash, TorrentState.METADATA_DOWNLOADING);
} else {
updateDatabaseTorrent(hash, TorrentState.DOWNLOADING);
}
}
public void startTorrent(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/start", hashes);
getResponseBodyOrError(req.get());
List<ITorrent> status = getTorrentStatus(hashes);
List<String> metadataHashes = new ArrayList<String>();
List<String> normalHashes = new ArrayList<String>();
List<String> seedingHashes = new ArrayList<String>();
for (ITorrent it : status) {
String hash = it.getTorrentHash();
if (it.isSeeding()) {
seedingHashes.add(hash);
} else if (it.isMetadataDownloading()) {
metadataHashes.add(hash);
} else {
normalHashes.add(hash);
}
}
updateDatabaseTorrent(metadataHashes, TorrentState.METADATA_DOWNLOADING);
updateDatabaseTorrent(normalHashes, TorrentState.DOWNLOADING);
updateDatabaseTorrent(seedingHashes, TorrentState.SEEDING);
}
public void stopTorrent(String hash) {
WSRequest req = node.getWebService("/torrents/stop", hash);
getResponseBodyOrError(req.get());
updateDatabaseTorrent(hash, TorrentState.PAUSED);
}
public void stopTorrent(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/stop", hashes);
getResponseBodyOrError(req.get());
updateDatabaseTorrent(hashes, TorrentState.PAUSED);
}
public ITorrent getTorrentStatus(String hash) {
List<String> list = new ArrayList<String>();
list.add(hash);
return getTorrentStatus(list).get(0);
}
public List<ITorrent> getTorrentStatus(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/status", hashes);
JsonObject body = getResponseBodyOrError(req.get()).getAsJsonObject();
JsonArray list = body.getAsJsonArray("status");
return parseToList(list);
}
public List<IPeer> getTorrentPeers(String hash) {
return getTorrentPeers(Arrays.asList(new String[] { hash })).get(hash);
}
public Map<String, List<IPeer>> getTorrentPeers(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/peers", hashes);
JsonObject peers = getResponseBodyOrError(req.get()).getAsJsonObject().getAsJsonObject("peers");
java.lang.reflect.Type listType = new TypeToken<HashMap<String, ArrayList<NodePeer>>>() {}.getType();
Map<String, List<IPeer>> ret = Util.getGson().fromJson(peers, listType);
return ret;
}
public List<ITracker> getTorrentTrackers(String hash) {
return getTorrentTrackers(Arrays.asList(new String[] { hash })).get(hash);
}
public Map<String, List<ITracker>> getTorrentTrackers(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/trackers", hashes);
JsonObject files = getResponseBodyOrError(req.get()).getAsJsonObject().getAsJsonObject("trackers");
java.lang.reflect.Type listType = new TypeToken<HashMap<String, ArrayList<NodeTracker>>>() {}.getType();
Map<String, List<ITracker>> ret = Util.getGson().fromJson(files, listType);
return ret;
}
public List<IFile> getTorrentFiles(String hash) {
return getTorrentFiles(Arrays.asList(new String[] { hash })).get(hash);
}
public Map<String, List<IFile>> getTorrentFiles(List<String> hashes) {
WSRequest req = node.getWebService("/torrents/files", hashes);
JsonObject files = getResponseBodyOrError(req.get()).getAsJsonObject().getAsJsonObject("files");
java.lang.reflect.Type listType = new TypeToken<HashMap<String, ArrayList<NodeFile>>>() {}.getType();
Map<String, List<IFile>> ret = Util.getGson().fromJson(files, listType);
for (String hash : ret.keySet()) {
List<IFile> li = ret.get(hash);
for (IFile f : li) {
NodeFile nf = (NodeFile) f;
nf.setNode(this.node);
}
}
return ret;
}
public void modifyTorrentFiles(String hash, List<IFile> files) {
throw new UnsupportedOperationException("Not supported yet.");
}
public void modifyTorrent(String hash, double seedRatio, long uploadLimitBytes, long downloadLimitBytes) {
throw new UnsupportedOperationException("Not supported yet.");
}
public List<ITorrent> listTorrents() {
return list(false);
}
public List<ITorrent> listRecentlyActiveTorrents() {
return list(true);
}
public ISessionStatistics getSessionStatistics() {
throw new UnsupportedOperationException("Not supported yet.");
}
private List<ITorrent> list(boolean recentlyActive) {
WSRequest req = node.getWebService("/torrents/list");
if (recentlyActive) {
req.setParameter("recentlyActive", true);
}
JsonArray body = getResponseBodyOrError(req.get()).getAsJsonArray();
return parseToList(body);
}
private List<ITorrent> parseToList(JsonArray list) {
java.lang.reflect.Type listType = new TypeToken<ArrayList<NodeTorrent>>() {}.getType();
List<NodeTorrent> ret = Util.getGson().fromJson(list, listType);
for (NodeTorrent nt : ret) {
nt.setNode(this.node);
}
return new ArrayList<ITorrent>(ret);
}
/* this exists so that the database (and therefore, the UI) is updated immediately when there is a state change
* instead of the user waiting 10 seconds for it to take effect when the next node poll occurs
* Also, some actions eg 'pausing' a torrent actually take a while to complete,
* so forceState instantly updates the state so the UI can be updated right away
*/
private void updateDatabaseTorrent(String hash, TorrentState forceState) {
List<String> hashes = new ArrayList<String>();
hashes.add(hash);
updateDatabaseTorrent(hashes, forceState);
}
private void updateDatabaseTorrent(List<String> hashes, TorrentState forceState) {
List<Torrent> torrents = Torrent.getByHash(hashes);
for (Torrent t : torrents) {
t.setStatus(forceState);
}
Torrent.batch().update(torrents);
}
private JsonElement getResponseBodyOrError(HttpResponse res) {
return this.node.handleWebServiceResponse(res);
/*
if (res.success()) {
try {
JsonObject ob = res.getJson().getAsJsonObject();
if (ob.has("error")) {
throw new MessageException(ob.get("error").getAsString());
}
return ob.get("data");
} catch (Exception ex) {
throw new MessageException(ex.getMessage());
}
}
throw new MessageException(res.getStatusText());*/
}
}