Compare commits
5 Commits
i2psnark-r
...
i2psnark-r
Author | SHA1 | Date | |
---|---|---|---|
4577202ba4 | |||
f48d7fc412 | |||
2eea36dc3a | |||
878613c214 | |||
24a9d311f4 |
23
CHANGES.txt
23
CHANGES.txt
@ -1,2 +1,23 @@
|
|||||||
|
* 2017-03-23 0.3
|
||||||
|
- Implement torrent-add
|
||||||
|
- Fix stats for magnets and downloads
|
||||||
|
- Replace logo
|
||||||
|
- Hide search button, doesn't work
|
||||||
|
- Rename console link
|
||||||
|
|
||||||
|
* 2017-03-21 0.2
|
||||||
|
- Implement percentDone
|
||||||
|
- Implement startDate, requires 0.9.29-9
|
||||||
|
- Return empty values for tag-uids and tag-get-list
|
||||||
|
- Fix values for activityDateRelative and downloadedEver
|
||||||
|
- Fix status flags for choking/choked
|
||||||
|
- Set status correctly for a starting seeder and for magnet
|
||||||
|
- Return ?? for creator if not defined
|
||||||
|
- Shorten peer address so it will fit in UI
|
||||||
|
- Don't add primary tracker if we have a list, to not dup
|
||||||
|
- Set size to 1 for magnet download
|
||||||
|
- Don't build update version of plugin
|
||||||
|
- Add license description for Transmission UI
|
||||||
|
|
||||||
* 2017-03-20 0.1
|
* 2017-03-20 0.1
|
||||||
- Initial version
|
- Initial version, requires 0.9.29-8
|
||||||
|
@ -18,6 +18,9 @@ Transmission:
|
|||||||
Copyright (c) Transmission authors and contributors
|
Copyright (c) Transmission authors and contributors
|
||||||
See licenses/Transmission.txt
|
See licenses/Transmission.txt
|
||||||
|
|
||||||
|
Transmission/Vuze web UI:
|
||||||
|
See licenses/LICENSE-GPLv2.txt
|
||||||
|
|
||||||
JSON:
|
JSON:
|
||||||
LGPLv2.1
|
LGPLv2.1
|
||||||
See licenses/JSON.txt
|
See licenses/JSON.txt
|
||||||
|
15
build.xml
15
build.xml
@ -11,15 +11,12 @@
|
|||||||
<target name="plugin" depends="war">
|
<target name="plugin" depends="war">
|
||||||
<!-- get version number -->
|
<!-- get version number -->
|
||||||
<buildnumber file="scripts/build.number" />
|
<buildnumber file="scripts/build.number" />
|
||||||
<property name="release.number" value="0.1" />
|
<property name="release.number" value="0.3" />
|
||||||
|
|
||||||
<!-- make the update xpi2p -->
|
<!-- we don't bother with an update plugin, everything is in a single war -->
|
||||||
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
|
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
|
||||||
<copy file="README.txt" todir="plugin/" overwrite="true" />
|
<copy file="README.txt" todir="plugin/" overwrite="true" />
|
||||||
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
|
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
|
||||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
|
||||||
<arg value="update-only=true" />
|
|
||||||
</exec>
|
|
||||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||||
<arg value="version=${release.number}-b${build.number}" />
|
<arg value="version=${release.number}-b${build.number}" />
|
||||||
</exec>
|
</exec>
|
||||||
@ -35,13 +32,7 @@
|
|||||||
</condition>
|
</condition>
|
||||||
</fail>
|
</fail>
|
||||||
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
|
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
|
||||||
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
|
<!-- make the install su3 -->
|
||||||
<arg value="plugin" />
|
|
||||||
</exec>
|
|
||||||
<move file="i2psnark-rpc.xpi2p" tofile="i2psnark-rpc-update.xpi2p" overwrite="true" />
|
|
||||||
<move file="i2psnark-rpc.su3" tofile="i2psnark-rpc-update.su3" overwrite="true" />
|
|
||||||
|
|
||||||
<!-- make the install xpi2p -->
|
|
||||||
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
|
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
|
||||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||||
<arg value="version=${release.number}-b${build.number}" />
|
<arg value="version=${release.number}-b${build.number}" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name=i2psnark-rpc
|
name=i2psnark-rpc
|
||||||
signer=zzz-plugin@mail.i2p
|
signer=zzz-plugin@mail.i2p
|
||||||
consoleLinkName=Transmission
|
consoleLinkName=I2PSnark Remote
|
||||||
consoleLinkURL=/transmission/web/
|
consoleLinkURL=/transmission/web/
|
||||||
description=RPC and Web UI for i2psnark
|
description=RPC and Web UI for i2psnark
|
||||||
author=zzz@mail.i2p
|
author=zzz@mail.i2p
|
||||||
|
357
src/java/org/klomp/snark/rpc/FetchAndAdd.java
Normal file
357
src/java/org/klomp/snark/rpc/FetchAndAdd.java
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
package org.klomp.snark.rpc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Released into the public domain
|
||||||
|
* with no warranty of any kind, either expressed or implied.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.client.streaming.I2PSocketEepGet;
|
||||||
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
|
import net.i2p.crypto.SHA1;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.EepGet;
|
||||||
|
import net.i2p.util.I2PAppThread;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.SecureFile;
|
||||||
|
|
||||||
|
import org.klomp.snark.I2PSnarkUtil;
|
||||||
|
import org.klomp.snark.MetaInfo;
|
||||||
|
import org.klomp.snark.Snark;
|
||||||
|
import org.klomp.snark.SnarkManager;
|
||||||
|
import org.klomp.snark.Storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cancellable torrent file downloader.
|
||||||
|
* We extend Snark so its status may be easily listed in the
|
||||||
|
* web table without adding a lot of code there.
|
||||||
|
*
|
||||||
|
* Upon successful download, this Snark will be deleted and
|
||||||
|
* a "real" Snark created.
|
||||||
|
*
|
||||||
|
* The methods return values similar to a Snark in magnet mode.
|
||||||
|
* A fake info hash, which is the SHA1 of the URL, is returned
|
||||||
|
* to prevent duplicates.
|
||||||
|
*
|
||||||
|
* This Snark may be stopped and restarted, although a partially
|
||||||
|
* downloaded file is discarded.
|
||||||
|
*
|
||||||
|
* @since 0.9.1 Moved from I2PSnarkUtil
|
||||||
|
*/
|
||||||
|
public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnable {
|
||||||
|
|
||||||
|
private final I2PAppContext _ctx;
|
||||||
|
private final Log _log;
|
||||||
|
private final SnarkManager _mgr;
|
||||||
|
private final String _url;
|
||||||
|
private final byte[] _fakeHash;
|
||||||
|
private final String _name;
|
||||||
|
private final File _dataDir;
|
||||||
|
private volatile long _remaining = -1;
|
||||||
|
private volatile long _total = -1;
|
||||||
|
private volatile long _transferred;
|
||||||
|
private volatile boolean _isRunning;
|
||||||
|
private volatile boolean _active;
|
||||||
|
private volatile long _started;
|
||||||
|
private String _failCause;
|
||||||
|
private Thread _thread;
|
||||||
|
private EepGet _eepGet;
|
||||||
|
|
||||||
|
private static final int RETRIES = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caller should call _mgr.addDownloader(this), which
|
||||||
|
* will start things off.
|
||||||
|
*
|
||||||
|
* @param dataDir null to default to snark data directory
|
||||||
|
*/
|
||||||
|
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url, File dataDir) {
|
||||||
|
// magnet constructor
|
||||||
|
super(mgr.util(), "Torrent download",
|
||||||
|
null, null, null, null, null, false, null);
|
||||||
|
_ctx = ctx;
|
||||||
|
_log = ctx.logManager().getLog(FetchAndAdd.class);
|
||||||
|
_mgr = mgr;
|
||||||
|
_url = url;
|
||||||
|
_name = _t("Download torrent file from {0}", url);
|
||||||
|
_dataDir = dataDir;
|
||||||
|
byte[] fake = null;
|
||||||
|
try {
|
||||||
|
fake = SHA1.getInstance().digest(url.getBytes("ISO-8859-1"));
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
_fakeHash = fake;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set off by startTorrent()
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
File file = get();
|
||||||
|
if (!_isRunning) // stopped?
|
||||||
|
return;
|
||||||
|
_isRunning = false;
|
||||||
|
if (file != null && file.exists() && file.length() > 0) {
|
||||||
|
// remove this in snarks
|
||||||
|
_mgr.deleteMagnet(this);
|
||||||
|
add(file);
|
||||||
|
} else {
|
||||||
|
_mgr.addMessageNoEscape(_t("Torrent was not retrieved from {0}", urlify(_url)) +
|
||||||
|
((_failCause != null) ? (": " + DataHelper.stripHTML(_failCause)) : ""));
|
||||||
|
}
|
||||||
|
if (file != null)
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copied from I2PSnarkUtil so we may add ourselves as a status listener
|
||||||
|
* @return null on failure
|
||||||
|
*/
|
||||||
|
private File get() {
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Fetching [" + _url + "]");
|
||||||
|
File out = null;
|
||||||
|
try {
|
||||||
|
out = SecureFile.createTempFile("torrentFile", null, _mgr.util().getTempDir());
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_log.error("temp file error", ioe);
|
||||||
|
_mgr.addMessage("Temp file error: " + ioe);
|
||||||
|
if (out != null)
|
||||||
|
out.delete();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
out.deleteOnExit();
|
||||||
|
|
||||||
|
if (!_mgr.util().connected()) {
|
||||||
|
_mgr.addMessage(_t("Opening the I2P tunnel"));
|
||||||
|
if (!_mgr.util().connect())
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
I2PSocketManager manager = _mgr.util().getSocketManager();
|
||||||
|
if (manager == null)
|
||||||
|
return null;
|
||||||
|
_eepGet = new I2PSocketEepGet(_ctx, manager, RETRIES, out.getAbsolutePath(), _url);
|
||||||
|
_eepGet.addStatusListener(this);
|
||||||
|
_eepGet.addHeader("User-Agent", I2PSnarkUtil.EEPGET_USER_AGENT);
|
||||||
|
if (_eepGet.fetch()) {
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Fetch successful [" + _url + "]: size=" + out.length());
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Fetch failed [" + _url + ']');
|
||||||
|
out.delete();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell SnarkManager to copy the torrent file over and add it to the Snarks list.
|
||||||
|
* This Snark may then be deleted.
|
||||||
|
*/
|
||||||
|
private void add(File file) {
|
||||||
|
FileInputStream in = null;
|
||||||
|
try {
|
||||||
|
in = new FileInputStream(file);
|
||||||
|
byte[] fileInfoHash = new byte[20];
|
||||||
|
String name = MetaInfo.getNameAndInfoHash(in, fileInfoHash);
|
||||||
|
try { in.close(); } catch (IOException ioe) {}
|
||||||
|
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
|
||||||
|
if (snark != null) {
|
||||||
|
_mgr.addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalName = Storage.filterName(name);
|
||||||
|
name = originalName + ".torrent";
|
||||||
|
File torrentFile = new File(_mgr.getDataDir(), name);
|
||||||
|
|
||||||
|
String canonical = torrentFile.getCanonicalPath();
|
||||||
|
|
||||||
|
if (torrentFile.exists()) {
|
||||||
|
if (_mgr.getTorrent(canonical) != null)
|
||||||
|
_mgr.addMessage(_t("Torrent already running: {0}", name));
|
||||||
|
else
|
||||||
|
_mgr.addMessage(_t("Torrent already in the queue: {0}", name));
|
||||||
|
} else {
|
||||||
|
// This may take a LONG time to create the storage.
|
||||||
|
_mgr.copyAndAddTorrent(file, canonical, _dataDir);
|
||||||
|
snark = _mgr.getTorrentByBaseName(originalName);
|
||||||
|
if (snark != null)
|
||||||
|
snark.startTorrent();
|
||||||
|
else
|
||||||
|
throw new IOException("Unknown error - check logs");
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
_mgr.addMessageNoEscape(_t("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
|
||||||
|
} catch (OutOfMemoryError oom) {
|
||||||
|
_mgr.addMessageNoEscape(_t("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
|
||||||
|
} finally {
|
||||||
|
try { if (in != null) in.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snark overrides so all the buttons and stats on the web page work
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void startTorrent() {
|
||||||
|
if (_isRunning)
|
||||||
|
return;
|
||||||
|
// reset counters in case starting a second time
|
||||||
|
_remaining = -1;
|
||||||
|
// leave the total if we knew it before
|
||||||
|
//_total = -1;
|
||||||
|
_transferred = 0;
|
||||||
|
_failCause = null;
|
||||||
|
_started = _util.getContext().clock().now();
|
||||||
|
_isRunning = true;
|
||||||
|
_active = false;
|
||||||
|
_thread = new I2PAppThread(this, "Torrent File EepGet", true);
|
||||||
|
_thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void stopTorrent() {
|
||||||
|
if (_thread != null && _isRunning) {
|
||||||
|
if (_eepGet != null)
|
||||||
|
_eepGet.stopFetching();
|
||||||
|
_thread.interrupt();
|
||||||
|
}
|
||||||
|
_isRunning = false;
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStopped() {
|
||||||
|
return !_isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getInfoHash() {
|
||||||
|
return _fakeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return torrent file size or -1
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getTotalLength() {
|
||||||
|
return _total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return -1 when done so the web will list us as "complete" instead of "seeding"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getRemainingLength() {
|
||||||
|
long rv = _remaining;
|
||||||
|
return rv > 0 ? rv : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return torrent file bytes remaining or -1
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getNeededLength() {
|
||||||
|
return _remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDownloadRate() {
|
||||||
|
if (_isRunning && _active) {
|
||||||
|
long time = _ctx.clock().now() - _started;
|
||||||
|
if (time > 1000) {
|
||||||
|
long rv = (_transferred * 1000) / time;
|
||||||
|
if (rv >= 100)
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDownloaded() {
|
||||||
|
return _total - _remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeerCount() {
|
||||||
|
return (_isRunning && _active && _transferred > 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrackerSeenPeers() {
|
||||||
|
return (_transferred > 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End Snark overrides
|
||||||
|
|
||||||
|
// EepGet status listeners to maintain the state for the web page
|
||||||
|
|
||||||
|
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||||
|
if (bytesRemaining >= 0) {
|
||||||
|
_remaining = bytesRemaining;
|
||||||
|
}
|
||||||
|
_transferred = bytesTransferred;
|
||||||
|
if (cause != null)
|
||||||
|
_failCause = cause.toString();
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||||
|
if (bytesRemaining >= 0) {
|
||||||
|
_remaining = bytesRemaining;
|
||||||
|
_total = bytesRemaining + currentWrite + alreadyTransferred;
|
||||||
|
}
|
||||||
|
_transferred = bytesTransferred;
|
||||||
|
_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||||
|
if (bytesRemaining >= 0) {
|
||||||
|
_remaining = bytesRemaining;
|
||||||
|
_total = bytesRemaining + alreadyTransferred;
|
||||||
|
}
|
||||||
|
_transferred = bytesTransferred;
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||||
|
if (bytesRemaining >= 0) {
|
||||||
|
_remaining = bytesRemaining;
|
||||||
|
}
|
||||||
|
_transferred = bytesTransferred;
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||||
|
|
||||||
|
public void attempting(String url) {}
|
||||||
|
|
||||||
|
// End of EepGet status listeners
|
||||||
|
|
||||||
|
private String _t(String s) {
|
||||||
|
return _mgr.util().getString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String _t(String s, String o) {
|
||||||
|
return _mgr.util().getString(s, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String urlify(String s) {
|
||||||
|
return UIUtil.urlify(s);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
package org.klomp.snark.rpc;
|
package org.klomp.snark.rpc;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
|
||||||
import org.klomp.snark.PeerID;
|
import org.klomp.snark.PeerID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,4 +89,69 @@ class UIUtil {
|
|||||||
}
|
}
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is "a" equal to "b",
|
||||||
|
* or is "a" a directory and a parent of file or directory "b",
|
||||||
|
* canonically speaking?
|
||||||
|
*
|
||||||
|
* @since 0.9.15
|
||||||
|
*/
|
||||||
|
public static boolean isParentOf(File a, File b) {
|
||||||
|
try {
|
||||||
|
a = a.getCanonicalFile();
|
||||||
|
b = b.getCanonicalFile();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.equals(b))
|
||||||
|
return true;
|
||||||
|
if (!a.isDirectory())
|
||||||
|
return false;
|
||||||
|
// easy case
|
||||||
|
if (!b.getPath().startsWith(a.getPath()))
|
||||||
|
return false;
|
||||||
|
// dir by dir
|
||||||
|
while (!a.equals(b)) {
|
||||||
|
b = b.getParentFile();
|
||||||
|
if (b == null)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for a full URL. For a path only, use encodePath().
|
||||||
|
* @since 0.7.14
|
||||||
|
*/
|
||||||
|
static String urlify(String s) {
|
||||||
|
return urlify(s, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for a full URL. For a path only, use encodePath().
|
||||||
|
* @since 0.9
|
||||||
|
*/
|
||||||
|
private static String urlify(String s, int max) {
|
||||||
|
StringBuilder buf = new StringBuilder(256);
|
||||||
|
// browsers seem to work without doing this but let's be strict
|
||||||
|
String link = urlEncode(s);
|
||||||
|
String display;
|
||||||
|
if (s.length() <= max)
|
||||||
|
display = DataHelper.escapeHTML(link);
|
||||||
|
else
|
||||||
|
display = DataHelper.escapeHTML(s.substring(0, max)) + "…";
|
||||||
|
buf.append("<a href=\"").append(link).append("\">").append(display).append("</a>");
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for a full URL. For a path only, use encodePath().
|
||||||
|
* @since 0.8.13
|
||||||
|
*/
|
||||||
|
private static String urlEncode(String s) {
|
||||||
|
return s.replace(";", "%3B").replace("&", "&").replace(" ", "%20")
|
||||||
|
.replace("<", "%3C").replace(">", "%3E")
|
||||||
|
.replace("[", "%5B").replace("]", "%5D");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.klomp.snark.rpc;
|
package org.klomp.snark.rpc;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@ -31,6 +32,7 @@ import java.text.DateFormat;
|
|||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -40,6 +42,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -61,11 +64,13 @@ import net.i2p.data.Base64;
|
|||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.servlet.RequestWrapper;
|
import net.i2p.servlet.RequestWrapper;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
import net.i2p.util.SecureFile;
|
||||||
|
|
||||||
import org.gudy.azureus2.plugins.download.DownloadException;
|
import org.gudy.azureus2.plugins.download.DownloadException;
|
||||||
|
|
||||||
import org.klomp.snark.BitField;
|
import org.klomp.snark.BitField;
|
||||||
import org.klomp.snark.I2PSnarkUtil;
|
import org.klomp.snark.I2PSnarkUtil;
|
||||||
|
import org.klomp.snark.MagnetURI;
|
||||||
import org.klomp.snark.MetaInfo;
|
import org.klomp.snark.MetaInfo;
|
||||||
import org.klomp.snark.Peer;
|
import org.klomp.snark.Peer;
|
||||||
import org.klomp.snark.PeerID;
|
import org.klomp.snark.PeerID;
|
||||||
@ -833,13 +838,11 @@ XMWebUIPlugin {
|
|||||||
method_Session_Get(args, result);
|
method_Session_Get(args, result);
|
||||||
} else if ( method.equals( "session-stats" )) {
|
} else if ( method.equals( "session-stats" )) {
|
||||||
method_Session_Stats(args, result);
|
method_Session_Stats(args, result);
|
||||||
/****
|
|
||||||
} else if ( method.equals( "torrent-add" )) {
|
} else if ( method.equals( "torrent-add" )) {
|
||||||
String agent = MapUtils.getMapString(request.getHeaders(), "User-Agent", "");
|
String agent = request.getHeader("User-Agent");
|
||||||
boolean xmlEscape = agent.startsWith("Mozilla/");
|
boolean xmlEscape = agent != null && agent.startsWith("Mozilla/");
|
||||||
method_Torrent_Add(args, result, xmlEscape);
|
method_Torrent_Add(args, result, xmlEscape);
|
||||||
// this is handled within the torrent-add method: save_core_state = true;
|
// this is handled within the torrent-add method: save_core_state = true;
|
||||||
****/
|
|
||||||
} else if ( method.equals( "torrent-start-all" )) {
|
} else if ( method.equals( "torrent-start-all" )) {
|
||||||
checkUpdatePermissions();
|
checkUpdatePermissions();
|
||||||
_manager.startAllTorrents();
|
_manager.startAllTorrents();
|
||||||
@ -907,9 +910,11 @@ XMWebUIPlugin {
|
|||||||
} else if ( method.equals( "torrent-rename-path" )) {
|
} else if ( method.equals( "torrent-rename-path" )) {
|
||||||
// RPC v15
|
// RPC v15
|
||||||
method_Torrent_Rename_Path(args, result);
|
method_Torrent_Rename_Path(args, result);
|
||||||
|
*/
|
||||||
} else if ( method.equals( "tags-get-list" )) {
|
} else if ( method.equals( "tags-get-list" )) {
|
||||||
// Vuze RPC v3
|
// Vuze RPC v3
|
||||||
method_Tags_Get_List(args, result);
|
method_Tags_Get_List(args, result);
|
||||||
|
/*
|
||||||
} else if ( method.equals( "tags-lookup-start" )) {
|
} else if ( method.equals( "tags-lookup-start" )) {
|
||||||
method_Tags_Lookup_Start(args, result);
|
method_Tags_Lookup_Start(args, result);
|
||||||
} else if ( method.equals( "tags-lookup-get-results" )) {
|
} else if ( method.equals( "tags-lookup-get-results" )) {
|
||||||
@ -1637,6 +1642,7 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
map.put(id, o);
|
map.put(id, o);
|
||||||
}
|
}
|
||||||
|
****/
|
||||||
|
|
||||||
private void method_Tags_Get_List(Map args, Map result) {
|
private void method_Tags_Get_List(Map args, Map result) {
|
||||||
List fields = (List) args.get("fields");
|
List fields = (List) args.get("fields");
|
||||||
@ -1647,6 +1653,7 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
List<SortedMap<String, Object>> listTags =
|
List<SortedMap<String, Object>> listTags =
|
||||||
new ArrayList<SortedMap<String,Object>>();
|
new ArrayList<SortedMap<String,Object>>();
|
||||||
|
/****
|
||||||
TagManager tm = TagManagerFactory.getTagManager();
|
TagManager tm = TagManagerFactory.getTagManager();
|
||||||
List<TagType> tagTypes = tm.getTagTypes();
|
List<TagType> tagTypes = tm.getTagTypes();
|
||||||
for (TagType tagType : tagTypes) {
|
for (TagType tagType : tagTypes) {
|
||||||
@ -1719,6 +1726,7 @@ XMWebUIPlugin {
|
|||||||
listTags.add(map);
|
listTags.add(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
****/
|
||||||
String hc = Long.toHexString(longHashSimpleList(listTags));
|
String hc = Long.toHexString(longHashSimpleList(listTags));
|
||||||
result.put("tags-hc", hc);
|
result.put("tags-hc", hc);
|
||||||
String oldHC = MapUtils.getMapString(args, "tags-hc", null);
|
String oldHC = MapUtils.getMapString(args, "tags-hc", null);
|
||||||
@ -1726,7 +1734,6 @@ XMWebUIPlugin {
|
|||||||
result.put("tags", listTags);
|
result.put("tags", listTags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
****/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This method tests how much free space is available in a
|
This method tests how much free space is available in a
|
||||||
@ -2911,7 +2918,6 @@ XMWebUIPlugin {
|
|||||||
form of one of 3.3's tr_info objects with the
|
form of one of 3.3's tr_info objects with the
|
||||||
fields for id, name, and hashString.
|
fields for id, name, and hashString.
|
||||||
*/
|
*/
|
||||||
/****
|
|
||||||
private void
|
private void
|
||||||
method_Torrent_Add(
|
method_Torrent_Add(
|
||||||
final Map args,
|
final Map args,
|
||||||
@ -2923,46 +2929,41 @@ XMWebUIPlugin {
|
|||||||
String metainfoString = (String) args.get("metainfo");
|
String metainfoString = (String) args.get("metainfo");
|
||||||
byte[] metainfoBytes = null;
|
byte[] metainfoBytes = null;
|
||||||
if ( metainfoString != null ) {
|
if ( metainfoString != null ) {
|
||||||
metainfoBytes = Base64.decode( metainfoString.replaceAll("[\r\n]+", "") );
|
metainfoBytes = Base64.decode( metainfoString.replaceAll("[\r\n]+", ""), true );
|
||||||
//metainfoBytes = Base64.decode( metainfoString );
|
if (metainfoBytes == null)
|
||||||
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
|
throw new TextualException("bad metainfo base64");
|
||||||
if ( vfh != null ) {
|
|
||||||
VuzeFile vf = vfh.loadVuzeFile( metainfoBytes );
|
|
||||||
if ( vf != null ) {
|
|
||||||
VuzeFileComponent[] comps = vf.getComponents();
|
|
||||||
for ( VuzeFileComponent comp: comps ) {
|
|
||||||
if ( comp.getType() != VuzeFileComponent.COMP_TYPE_METASEARCH_TEMPLATE ) {
|
|
||||||
throw( new TextualException( "Unsupported Vuze File component type: " + comp.getTypeName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vfh.handleFiles( new VuzeFile[]{ vf }, VuzeFileComponent.COMP_TYPE_METASEARCH_TEMPLATE );
|
|
||||||
String added_templates = "";
|
|
||||||
for ( VuzeFileComponent comp: comps ) {
|
|
||||||
if ( comp.isProcessed()) {
|
|
||||||
Engine e = (Engine)comp.getData( Engine.VUZE_FILE_COMPONENT_ENGINE_KEY );
|
|
||||||
if ( e != null ) {
|
|
||||||
added_templates += (added_templates==""?"":", ") + e.getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( added_templates.length() == 0 ) {
|
|
||||||
throw( new TextualException( "No search template(s) added" ));
|
|
||||||
} else {
|
|
||||||
throw( new TextualException( "Installed search template(s): " + added_templates ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Snark torrent = null;
|
MetaInfo torrent = null;
|
||||||
Snark download = null;
|
Snark download = null;
|
||||||
String url = (String) args.get("filename");
|
String url = (String) args.get("filename");
|
||||||
final boolean add_stopped = getBoolean(args.get("paused"));
|
final boolean add_stopped = getBoolean(args.get("paused"));
|
||||||
String download_dir = (String) args.get("download-dir");
|
String download_dir = (String) args.get("download-dir");
|
||||||
final File file_Download_dir = download_dir == null ? null : new File(download_dir);
|
final File file_Download_dir = download_dir == null ? null : new SecureFile(download_dir);
|
||||||
|
if (file_Download_dir != null) {
|
||||||
|
// This code is copied from I2PSnarkServlet
|
||||||
|
if (!file_Download_dir.isAbsolute()) {
|
||||||
|
throw new TextualException(_t("Data directory must be an absolute path") + ": " + file_Download_dir);
|
||||||
|
}
|
||||||
|
if (!file_Download_dir.isDirectory() && !file_Download_dir.mkdirs()) {
|
||||||
|
throw new TextualException(_t("Data directory cannot be created") + ": " + file_Download_dir);
|
||||||
|
}
|
||||||
|
Collection<Snark> snarks = _manager.getTorrents();
|
||||||
|
for (Snark s : snarks) {
|
||||||
|
Storage storage = s.getStorage();
|
||||||
|
if (storage == null)
|
||||||
|
continue;
|
||||||
|
File sbase = storage.getBase();
|
||||||
|
if (UIUtil.isParentOf(sbase, file_Download_dir)) {
|
||||||
|
throw new TextualException(_t("Cannot add torrent {0} inside another torrent: {1}",
|
||||||
|
file_Download_dir.getAbsolutePath(), sbase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// peer-limit not used
|
// peer-limit not used
|
||||||
//getNumber(args.get("peer-limit"), 0);
|
//getNumber(args.get("peer-limit"), 0);
|
||||||
// bandwidthPriority not used
|
// bandwidthPriority not used
|
||||||
//getNumber(args.get("bandwidthPriority"), TR_PRI_NORMAL);
|
//getNumber(args.get("bandwidthPriority"), TR_PRI_NORMAL);
|
||||||
|
/****
|
||||||
final DownloadWillBeAddedListener add_listener =
|
final DownloadWillBeAddedListener add_listener =
|
||||||
new DownloadWillBeAddedListener() {
|
new DownloadWillBeAddedListener() {
|
||||||
public void initialised(Snark download) {
|
public void initialised(Snark download) {
|
||||||
@ -3008,6 +3009,7 @@ XMWebUIPlugin {
|
|||||||
DiskManagerFileInfo.PRIORITY_LOW);
|
DiskManagerFileInfo.PRIORITY_LOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't need priority-normal if they are normal by default.
|
// don't need priority-normal if they are normal by default.
|
||||||
// handle initial categories/tags
|
// handle initial categories/tags
|
||||||
try {
|
try {
|
||||||
@ -3034,164 +3036,52 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
TorrentManager torrentManager = plugin_interface.getTorrentManager();
|
****/
|
||||||
boolean duplicate = false;
|
boolean duplicate = false;
|
||||||
if ( metainfoBytes != null ) {
|
if ( metainfoBytes != null ) {
|
||||||
try {
|
torrent = new MetaInfo(new ByteArrayInputStream(metainfoBytes));
|
||||||
torrent = torrentManager.createFromBEncodedData( metainfoBytes);
|
download = _manager.getTorrentByInfoHash(torrent.getInfoHash());
|
||||||
SnarkManager dm = _manager;
|
duplicate = download != null;
|
||||||
download = dm.getDownload( torrent );
|
if ( download == null ) {
|
||||||
duplicate = download != null;
|
boolean success = _manager.addTorrent( torrent, null, null, file_Download_dir, add_stopped );
|
||||||
} catch (Throwable e) {
|
if (success)
|
||||||
e.printStackTrace();
|
download = _manager.getTorrentByInfoHash(torrent.getInfoHash());
|
||||||
//System.err.println("decode of " + new String(Base64.encode(metainfoBytes), "UTF8"));
|
|
||||||
throw (new IOException("torrent download failed: "
|
|
||||||
+ Debug.getNestedExceptionMessage(e)));
|
|
||||||
}
|
}
|
||||||
} else if (url == null) {
|
} else if (url == null) {
|
||||||
throw (new IOException("url missing"));
|
throw (new IOException("url missing"));
|
||||||
} else {
|
} else {
|
||||||
url = url.trim().replaceAll(" ", "%20");
|
url = url.trim().replaceAll(" ", "%20");
|
||||||
// hack due to core bug - have to add a bogus arg onto magnet uris else they fail to parse
|
|
||||||
String lc_url = url.toLowerCase( Locale.US );
|
|
||||||
if ( lc_url.startsWith("magnet:")) {
|
|
||||||
url += "&dummy_param=1";
|
|
||||||
} else if (!lc_url.startsWith("http")) {
|
|
||||||
url = UrlUtils.parseTextForURL(url, true, true);
|
|
||||||
}
|
|
||||||
byte[] hashFromMagnetURI = getHashFromMagnetURI(url);
|
byte[] hashFromMagnetURI = getHashFromMagnetURI(url);
|
||||||
if (hashFromMagnetURI != null) {
|
if (hashFromMagnetURI != null) {
|
||||||
org.gudy.azureus2.plugins.download.SnarkManager dm = _manager;
|
download = _manager.getTorrentByInfoHash(hashFromMagnetURI);
|
||||||
download = dm.getDownload(hashFromMagnetURI);
|
|
||||||
duplicate = download != null;
|
duplicate = download != null;
|
||||||
}
|
}
|
||||||
if (download == null) {
|
if (download == null) {
|
||||||
URL torrent_url;
|
// This code is copied from I2PSnarkServlet
|
||||||
try {
|
if (url.startsWith("http://")) {
|
||||||
torrent_url = new URL(url);
|
download = new FetchAndAdd(_context, _manager, url, file_Download_dir);
|
||||||
} catch (MalformedURLException mue) {
|
_manager.addDownloader(download);
|
||||||
throw new TextualException("The torrent URI was not valid");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final TorrentDownloader dl = torrentManager.getURLDownloader(torrent_url, null, null);
|
|
||||||
Object cookies = args.get("cookies");
|
|
||||||
if ( cookies != null ) {
|
|
||||||
dl.setRequestProperty("URL_Cookie", cookies);
|
|
||||||
}
|
|
||||||
boolean is_magnet = torrent_url.getProtocol().equalsIgnoreCase( "magnet" );
|
|
||||||
if ( is_magnet ) {
|
|
||||||
TimerEvent magnet_event = null;
|
|
||||||
final Object[] f_result = { null };
|
|
||||||
try {
|
|
||||||
final AESemaphore sem = new AESemaphore( "magnetsem" );
|
|
||||||
final URL f_torrent_url = torrent_url;
|
|
||||||
final String f_name = (String) args.get("name");
|
|
||||||
magnet_event = SimpleTimer.addEvent(
|
|
||||||
"magnetcheck",
|
|
||||||
_context.clock().getOffsetTime( 10*1000 ),
|
|
||||||
new TimerEventPerformer()
|
|
||||||
{
|
|
||||||
public void
|
|
||||||
perform(
|
|
||||||
TimerEvent event )
|
|
||||||
{
|
|
||||||
synchronized( f_result ) {
|
|
||||||
if ( f_result[0] != null ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MagnetSnark magnet_download = new MagnetDownload( f_torrent_url, f_name );
|
|
||||||
byte[] hash = magnet_download.getInfoHash();
|
|
||||||
synchronized( magnet_downloads ) {
|
|
||||||
boolean duplicate = false;
|
|
||||||
Iterator<MagnetDownload> it = magnet_downloads.iterator();
|
|
||||||
while( it.hasNext()) {
|
|
||||||
MagnetSnark md = it.next();
|
|
||||||
if ( hash.length > 0 && Arrays.equals( hash, md.getInfoHash())) {
|
|
||||||
if ( md.getError() == null ) {
|
|
||||||
duplicate = true;
|
|
||||||
magnet_download = md;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
it.remove();
|
|
||||||
addRecentlyRemoved( md );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( !duplicate ) {
|
|
||||||
magnet_downloads.add( magnet_download );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f_result[0] = magnet_download;
|
|
||||||
}
|
|
||||||
sem.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
new AEThread2( "magnetasync" )
|
|
||||||
{
|
|
||||||
public void
|
|
||||||
run()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
MetaInfo torrent = dl.download("UTF-8");
|
|
||||||
synchronized( f_result ) {
|
|
||||||
if ( f_result[0] == null ) {
|
|
||||||
f_result[0] = torrent;
|
|
||||||
} else {
|
|
||||||
MagnetSnark md = (MagnetDownload)f_result[0];
|
|
||||||
boolean already_removed;
|
|
||||||
synchronized( magnet_downloads ) {
|
|
||||||
already_removed = !magnet_downloads.remove( md );
|
|
||||||
}
|
|
||||||
if ( !already_removed ) {
|
|
||||||
addRecentlyRemoved( md );
|
|
||||||
addTorrent( torrent, file_Download_dir, add_stopped, add_listener );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch( Throwable e ) {
|
|
||||||
synchronized( f_result ) {
|
|
||||||
if ( f_result[0] == null ) {
|
|
||||||
f_result[0] = e;
|
|
||||||
} else {
|
|
||||||
MagnetSnark md = (MagnetDownload)f_result[0];
|
|
||||||
md.setError( e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
sem.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
sem.reserve();
|
|
||||||
Object res;
|
|
||||||
synchronized( f_result ) {
|
|
||||||
res = f_result[0];
|
|
||||||
}
|
|
||||||
if ( res instanceof Snark ) {
|
|
||||||
torrent = (Torrent)res;
|
|
||||||
} else if ( res instanceof Throwable ) {
|
|
||||||
throw((Throwable)res);
|
|
||||||
} else {
|
|
||||||
download = (MagnetDownload)res;
|
|
||||||
torrent = null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if ( magnet_event != null ) {
|
|
||||||
magnet_event.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
torrent = dl.download("UTF-8");
|
if (url.startsWith(MagnetURI.MAGNET) || url.startsWith(MagnetURI.MAGGOT)) {
|
||||||
|
addMagnet(url, file_Download_dir);
|
||||||
|
} else if (url.length() == 40 && url.replaceAll("[a-fA-F0-9]", "").length() == 0) {
|
||||||
|
// hex
|
||||||
|
url = url.toUpperCase(Locale.US);
|
||||||
|
addMagnet(MagnetURI.MAGNET_FULL + url, file_Download_dir);
|
||||||
|
} else if (url.length() == 32 && url.replaceAll("[a-zA-Z2-7]", "").length() == 0) {
|
||||||
|
// b32
|
||||||
|
url = url.toUpperCase(Locale.US);
|
||||||
|
addMagnet(MagnetURI.MAGNET_FULL + url, file_Download_dir);
|
||||||
|
} else {
|
||||||
|
throw new TextualException("The torrent URI was not valid");
|
||||||
|
}
|
||||||
|
download = _manager.getTorrentByInfoHash(hashFromMagnetURI);
|
||||||
|
if (download == null)
|
||||||
|
throw new TextualException("Magnet add failed"); //shouldn't happen
|
||||||
}
|
}
|
||||||
} catch( Throwable e ) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw( new IOException( Debug.getNestedExceptionMessage( e )));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( download == null ) {
|
// download must be non-null here, either the new torrent or the old duplicate
|
||||||
download = addTorrent( torrent, file_Download_dir, add_stopped, add_listener );
|
|
||||||
}
|
|
||||||
Map<String, Object> torrent_details = new HashMap<String, Object>();
|
Map<String, Object> torrent_details = new HashMap<String, Object>();
|
||||||
torrent_details.put("id", new Long(getID(download, true)));
|
torrent_details.put("id", new Long(getID(download, true)));
|
||||||
torrent_details.put("name", xmlEscape ? escapeXML(download.getName()) : download.getName());
|
torrent_details.put("name", xmlEscape ? escapeXML(download.getName()) : download.getName());
|
||||||
@ -3200,15 +3090,32 @@ XMWebUIPlugin {
|
|||||||
result.put(duplicate ? "torrent-duplicate" : "torrent-added", torrent_details);
|
result.put(duplicate ? "torrent-duplicate" : "torrent-added", torrent_details);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getHashFromMagnetURI(String magnetURI) {
|
private byte[] getHashFromMagnetURI(String magnetURI) {
|
||||||
Pattern patXT = Pattern.compile("xt=urn:(?:btih|sha1):([^&]+)");
|
try {
|
||||||
Matcher matcher = patXT.matcher(magnetURI);
|
MagnetURI magnet = new MagnetURI(_util, magnetURI);
|
||||||
if (matcher.find()) {
|
return magnet.getInfoHash();
|
||||||
return UrlUtils.decodeSHA1Hash(matcher.group(1));
|
} catch (IllegalArgumentException iae) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copied from I2PSnarkServlet
|
||||||
|
* @param url in base32 or hex
|
||||||
|
* @param dataDir null to default to snark data directory
|
||||||
|
* @since 0.8.4
|
||||||
|
*/
|
||||||
|
private void addMagnet(String url, File dataDir) {
|
||||||
|
try {
|
||||||
|
MagnetURI magnet = new MagnetURI(_util, url);
|
||||||
|
String name = magnet.getName();
|
||||||
|
byte[] ih = magnet.getInfoHash();
|
||||||
|
String trackerURL = magnet.getTrackerURL();
|
||||||
|
_manager.addMagnet(name, ih, trackerURL, true, dataDir);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new TextualException(_t("Invalid magnet URL {0}", url));
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
****/
|
|
||||||
|
|
||||||
private Map
|
private Map
|
||||||
method_Torrent_Get(
|
method_Torrent_Get(
|
||||||
@ -3324,7 +3231,10 @@ XMWebUIPlugin {
|
|||||||
// RPC v0
|
// RPC v0
|
||||||
// activityDate | number | tr_stat
|
// activityDate | number | tr_stat
|
||||||
//value = torrentGet_activityDate(core_download, true);
|
//value = torrentGet_activityDate(core_download, true);
|
||||||
value = 0;
|
if (download.isStopped())
|
||||||
|
value = 0L - (_context.clock().now() / 1000L);
|
||||||
|
else
|
||||||
|
value = 0;
|
||||||
} else if (field.equals("addedDate")) {
|
} else if (field.equals("addedDate")) {
|
||||||
// RPC v0
|
// RPC v0
|
||||||
// addedDate | number | tr_stat
|
// addedDate | number | tr_stat
|
||||||
@ -3360,10 +3270,13 @@ XMWebUIPlugin {
|
|||||||
} else if (field.equals("creator")) {
|
} else if (field.equals("creator")) {
|
||||||
// RPC v0
|
// RPC v0
|
||||||
// creator | string | tr_info
|
// creator | string | tr_info
|
||||||
if (t != null)
|
if (t != null) {
|
||||||
value = t.getCreatedBy();
|
value = t.getCreatedBy();
|
||||||
else
|
if (value == null)
|
||||||
value = "";
|
value = "??";
|
||||||
|
} else {
|
||||||
|
value = "??";
|
||||||
|
}
|
||||||
} else if (field.equals("dateCreated")) {
|
} else if (field.equals("dateCreated")) {
|
||||||
// RPC v0
|
// RPC v0
|
||||||
// dateCreated | number | tr_info
|
// dateCreated | number | tr_info
|
||||||
@ -3407,6 +3320,9 @@ XMWebUIPlugin {
|
|||||||
// downloadDir | string | tr_torrent
|
// downloadDir | string | tr_torrent
|
||||||
if (storage != null) {
|
if (storage != null) {
|
||||||
value = storage.getBase().getAbsolutePath();
|
value = storage.getBase().getAbsolutePath();
|
||||||
|
} else {
|
||||||
|
//value = "TBD";
|
||||||
|
value = _manager.getDataDir().getAbsolutePath();
|
||||||
}
|
}
|
||||||
} else if (field.equals("downloadedEver")) {
|
} else if (field.equals("downloadedEver")) {
|
||||||
// RPC v0
|
// RPC v0
|
||||||
@ -3416,7 +3332,14 @@ XMWebUIPlugin {
|
|||||||
* for this torrent. If you deleted the files and downloaded a second
|
* for this torrent. If you deleted the files and downloaded a second
|
||||||
* time, this will be 2*totalSize..
|
* time, this will be 2*totalSize..
|
||||||
*/
|
*/
|
||||||
value = download.getDownloaded();
|
// we don't track that, just give them what we have...
|
||||||
|
// unless if magnet, then give them the downloaded count
|
||||||
|
long total = download.getTotalLength();
|
||||||
|
long needed = download.getRemainingLength();
|
||||||
|
if (total >= 0 && needed >= 0)
|
||||||
|
value = total - needed;
|
||||||
|
else
|
||||||
|
value = download.getDownloaded();
|
||||||
} else if (field.equals("downloadLimit")
|
} else if (field.equals("downloadLimit")
|
||||||
|| field.equals("speed-limit-down")) {
|
|| field.equals("speed-limit-down")) {
|
||||||
// RPC v5 (alternate is from 'set' prior to v5 -- added for rogue clients)
|
// RPC v5 (alternate is from 'set' prior to v5 -- added for rogue clients)
|
||||||
@ -3534,7 +3457,7 @@ XMWebUIPlugin {
|
|||||||
if (needed >= 0)
|
if (needed >= 0)
|
||||||
value = needed;
|
value = needed;
|
||||||
else
|
else
|
||||||
value = 16384; // TODO
|
value = 1; // TODO
|
||||||
} else if (field.equals("magnetLink")) {
|
} else if (field.equals("magnetLink")) {
|
||||||
// TODO RPC v7
|
// TODO RPC v7
|
||||||
// magnetLink | number | n/a
|
// magnetLink | number | n/a
|
||||||
@ -3611,15 +3534,21 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
value = count;
|
value = count;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
} else if (field.equals("percentDone")) {
|
} else if (field.equals("percentDone")) {
|
||||||
// RPC v5
|
// RPC v5
|
||||||
// percentDone | double | tr_stat
|
// percentDone | double | tr_stat
|
||||||
// How much has been downloaded of the files the user wants. This differs
|
// How much has been downloaded of the files the user wants. This differs
|
||||||
// from percentComplete if the user wants only some of the torrent's files.
|
// from percentComplete if the user wants only some of the torrent's files.
|
||||||
// Range is [0..1]
|
// Range is [0..1]
|
||||||
value = core_download.getStats().getPercentDoneExcludingDND() / 1000.0f;
|
long needed = download.getNeededLength();
|
||||||
*/
|
if (needed < 0)
|
||||||
|
needed = download.getRemainingLength();
|
||||||
|
if (needed < 0) {
|
||||||
|
value = 0.0f;
|
||||||
|
} else {
|
||||||
|
long whenDone = download.getTotalLength() - download.getSkippedLength();
|
||||||
|
value = 1.0f - (needed / (float) whenDone);
|
||||||
|
}
|
||||||
} else if (field.equals("pieces")) {
|
} else if (field.equals("pieces")) {
|
||||||
// RPC v5
|
// RPC v5
|
||||||
value = torrentGet_pieces(download);
|
value = torrentGet_pieces(download);
|
||||||
@ -3637,12 +3566,13 @@ XMWebUIPlugin {
|
|||||||
value = 16384;
|
value = 16384;
|
||||||
} else if (field.equals("priorities")) {
|
} else if (field.equals("priorities")) {
|
||||||
value = torrentGet_priorities(download);
|
value = torrentGet_priorities(download);
|
||||||
/*
|
|
||||||
} else if (field.equals("queuePosition")) {
|
} else if (field.equals("queuePosition")) {
|
||||||
// RPC v14
|
// RPC v14
|
||||||
// "queuePosition" | number position of this torrent in its queue [0...n)
|
// "queuePosition" | number position of this torrent in its queue [0...n)
|
||||||
|
/*
|
||||||
value = core_download.getPosition();
|
value = core_download.getPosition();
|
||||||
*/
|
*/
|
||||||
|
value = 0;
|
||||||
} else if (field.equals("rateDownload")) {
|
} else if (field.equals("rateDownload")) {
|
||||||
// rateSnark (B/s) | number | tr_stat
|
// rateSnark (B/s) | number | tr_stat
|
||||||
value = download.getDownloadRate();
|
value = download.getDownloadRate();
|
||||||
@ -3690,21 +3620,28 @@ XMWebUIPlugin {
|
|||||||
* if only some of the torrent's files are wanted.
|
* if only some of the torrent's files are wanted.
|
||||||
* [0...tr_info.totalSize]
|
* [0...tr_info.totalSize]
|
||||||
**/
|
**/
|
||||||
value = download.getTotalLength() - download.getSkippedLength();
|
value = Math.max(download.getTotalLength() - download.getSkippedLength(), 1L);
|
||||||
} else if (field.equals("startDate")) {
|
} else if (field.equals("startDate")) {
|
||||||
// When the torrent was last started.
|
// When the torrent was last started.
|
||||||
//value = stats.getTimeStarted() / 1000;
|
try {
|
||||||
value = 0;
|
value = download.getStartedTime() / 1000L;
|
||||||
|
} catch (Throwable thr) {
|
||||||
|
// plugin supported in 0.9.29-8, method added in 0.9.29-9
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
} else if (field.equals("status")) {
|
} else if (field.equals("status")) {
|
||||||
if (download.isStarting())
|
if (download.isStarting()) {
|
||||||
value = TR_STATUS_DOWNLOAD_WAIT;
|
if (download.getRemainingLength() == 0)
|
||||||
else if (download.isStopped())
|
value = TR_STATUS_SEED_WAIT;
|
||||||
|
else
|
||||||
|
value = TR_STATUS_DOWNLOAD_WAIT;
|
||||||
|
} else if (download.isStopped())
|
||||||
value = TR_STATUS_STOPPED;
|
value = TR_STATUS_STOPPED;
|
||||||
else if (download.isChecking())
|
else if (download.isChecking())
|
||||||
value = TR_STATUS_CHECK;
|
value = TR_STATUS_CHECK;
|
||||||
else if (download.isAllocating())
|
else if (download.isAllocating())
|
||||||
value = TR_STATUS_DOWNLOAD;
|
value = TR_STATUS_DOWNLOAD_WAIT;
|
||||||
else if (download.getRemainingLength() <= 0)
|
else if (download.getRemainingLength() == 0)
|
||||||
value = TR_STATUS_SEED;
|
value = TR_STATUS_SEED;
|
||||||
else
|
else
|
||||||
value = TR_STATUS_DOWNLOAD;
|
value = TR_STATUS_DOWNLOAD;
|
||||||
@ -3720,13 +3657,14 @@ XMWebUIPlugin {
|
|||||||
boolean hack = agent != null && agent.contains("httpok"); // Torrnado
|
boolean hack = agent != null && agent.contains("httpok"); // Torrnado
|
||||||
value = torrentGet_trackers(download, hack);
|
value = torrentGet_trackers(download, hack);
|
||||||
} else if (field.equals("totalSize")) {
|
} else if (field.equals("totalSize")) {
|
||||||
value = download.getTotalLength();
|
value = Math.max(download.getTotalLength(), 1L);
|
||||||
} else if (field.equals("torrentFile")) {
|
} else if (field.equals("torrentFile")) {
|
||||||
// torrentFile | string | tr_info
|
// torrentFile | string | tr_info
|
||||||
// Path to torrent
|
// Path to torrent
|
||||||
value = download.getName();
|
value = download.getName();
|
||||||
} else if (field.equals("uploadedEver")) {
|
} else if (field.equals("uploadedEver")) {
|
||||||
// uploadedEver | number | tr_stat
|
// uploadedEver | number | tr_stat
|
||||||
|
// we don't persist this, just give them what we have sent this time
|
||||||
value = download.getUploaded();
|
value = download.getUploaded();
|
||||||
} else if (field.equals("uploadLimit") || field.equals("speed-limit-up")) {
|
} else if (field.equals("uploadLimit") || field.equals("speed-limit-up")) {
|
||||||
// RPC v5 (alternate is from 'set' prior to v5 -- added for rogue clients)
|
// RPC v5 (alternate is from 'set' prior to v5 -- added for rogue clients)
|
||||||
@ -3870,8 +3808,8 @@ XMWebUIPlugin {
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
} else if (field.equals("tag-uids")) {
|
} else if (field.equals("tag-uids")) {
|
||||||
|
/*
|
||||||
// azRPC
|
// azRPC
|
||||||
List<Long> listTags = new ArrayList<Long>();
|
List<Long> listTags = new ArrayList<Long>();
|
||||||
TagManager tm = TagManagerFactory.getTagManager();
|
TagManager tm = TagManagerFactory.getTagManager();
|
||||||
@ -3892,6 +3830,7 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
value = listTags;
|
value = listTags;
|
||||||
*/
|
*/
|
||||||
|
value = Collections.EMPTY_LIST;
|
||||||
} else {
|
} else {
|
||||||
if ( trace_param ) {
|
if ( trace_param ) {
|
||||||
log("Unhandled get-torrent field: " + field);
|
log("Unhandled get-torrent field: " + field);
|
||||||
@ -4176,7 +4115,9 @@ XMWebUIPlugin {
|
|||||||
String name = t.getAnnounce();
|
String name = t.getAnnounce();
|
||||||
if (name == null)
|
if (name == null)
|
||||||
name = download.getTrackerURL(); // from magnet URL
|
name = download.getTrackerURL(); // from magnet URL
|
||||||
if (name != null) {
|
List<List<String>> alist = t.getAnnounceList();
|
||||||
|
// don't add the primary if we have a list
|
||||||
|
if (name != null && (alist == null || alist.isEmpty())) {
|
||||||
if (hack && !name.contains("://")) {
|
if (hack && !name.contains("://")) {
|
||||||
name = "://" + name;
|
name = "://" + name;
|
||||||
}
|
}
|
||||||
@ -4190,7 +4131,6 @@ XMWebUIPlugin {
|
|||||||
map.put("tier", tier++);
|
map.put("tier", tier++);
|
||||||
trackers.add(map);
|
trackers.add(map);
|
||||||
}
|
}
|
||||||
List<List<String>> alist = t.getAnnounceList();
|
|
||||||
if (alist != null && !alist.isEmpty()) {
|
if (alist != null && !alist.isEmpty()) {
|
||||||
for (List<String> alist2 : alist) {
|
for (List<String> alist2 : alist) {
|
||||||
for (String name2 : alist2) {
|
for (String name2 : alist2) {
|
||||||
@ -4538,33 +4478,6 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
****/
|
****/
|
||||||
|
|
||||||
/**
|
|
||||||
* The last time we uploaded or downloaded piece data on this torrent.
|
|
||||||
*/
|
|
||||||
/****
|
|
||||||
private Object torrentGet_activityDate(SnarkManager download, boolean relative) {
|
|
||||||
int state = download.getState();
|
|
||||||
if (state == SnarkManager.STATE_SEEDING || state == SnarkManager.STATE_DOWNLOADING) {
|
|
||||||
int r = download.getStats().getTimeSinceLastDataReceivedInSeconds();
|
|
||||||
int s = download.getStats().getTimeSinceLastDataSentInSeconds();
|
|
||||||
long l;
|
|
||||||
if (r > 0 && s > 0) {
|
|
||||||
l = Math.min(r, s);
|
|
||||||
} else if (r < 0) {
|
|
||||||
l = s;
|
|
||||||
} else {
|
|
||||||
l = r;
|
|
||||||
}
|
|
||||||
if (relative) {
|
|
||||||
return -l;
|
|
||||||
}
|
|
||||||
// XXX THIS IS STUPID! Time on this machine won't be the same as the client..
|
|
||||||
return (_context.clock().now() / 1000) - l;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
****/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the torrent is running, but has been idle for long enough
|
* True if the torrent is running, but has been idle for long enough
|
||||||
* to be considered stalled.
|
* to be considered stalled.
|
||||||
@ -4606,7 +4519,14 @@ XMWebUIPlugin {
|
|||||||
boolean isDownloadingFrom = !peer.isChoked() && dlRate > 0;
|
boolean isDownloadingFrom = !peer.isChoked() && dlRate > 0;
|
||||||
boolean isUploadingTo = !peer.isChoking() && ulRate > 0;
|
boolean isUploadingTo = !peer.isChoking() && ulRate > 0;
|
||||||
Destination dest = peer.getDestination();
|
Destination dest = peer.getDestination();
|
||||||
String b32 = (dest != null) ? dest.toBase32() : "";
|
String b32;
|
||||||
|
if (dest != null) {
|
||||||
|
b32 = dest.toBase32();
|
||||||
|
// sadly, only about 25 chars fit nicely in the UI.
|
||||||
|
b32 = b32.substring(0, 12) + "..." + b32.substring(48);
|
||||||
|
} else {
|
||||||
|
b32 = "";
|
||||||
|
}
|
||||||
map.put("address", b32);
|
map.put("address", b32);
|
||||||
String client = UIUtil.getClientName(peer.getPeerID());
|
String client = UIUtil.getClientName(peer.getPeerID());
|
||||||
map.put("clientName", client);
|
map.put("clientName", client);
|
||||||
@ -4630,14 +4550,14 @@ XMWebUIPlugin {
|
|||||||
flagStr.append('D');
|
flagStr.append('D');
|
||||||
} else if (peer.isInteresting()) {
|
} else if (peer.isInteresting()) {
|
||||||
flagStr.append('d');
|
flagStr.append('d');
|
||||||
} else if (peer.isChoked()) {
|
} else if (!peer.isChoked()) {
|
||||||
flagStr.append('K');
|
flagStr.append('K');
|
||||||
}
|
}
|
||||||
if (isUploadingTo) {
|
if (isUploadingTo) {
|
||||||
flagStr.append('U');
|
flagStr.append('U');
|
||||||
} else if (peer.isInterested()) {
|
} else if (peer.isInterested()) {
|
||||||
flagStr.append('u');
|
flagStr.append('u');
|
||||||
} else if (peer.isChoking()) {
|
} else if (!peer.isChoking()) {
|
||||||
flagStr.append('?');
|
flagStr.append('?');
|
||||||
}
|
}
|
||||||
flagStr.append('E');
|
flagStr.append('E');
|
||||||
@ -5425,176 +5345,6 @@ XMWebUIPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
|
||||||
private class
|
|
||||||
MagnetDownload
|
|
||||||
implements Snark
|
|
||||||
{
|
|
||||||
private URL magnet_url;
|
|
||||||
private String name;
|
|
||||||
private byte[] hash;
|
|
||||||
private long create_time;
|
|
||||||
private Map<TorrentAttribute,Long> attributes = new HashMap<TorrentAttribute, Long>();
|
|
||||||
private String temp_dir = _util.getTempDir().getAbsolutePath();
|
|
||||||
private Throwable error;
|
|
||||||
|
|
||||||
private
|
|
||||||
MagnetDownload(
|
|
||||||
URL _magnet,
|
|
||||||
String friendlyName )
|
|
||||||
{
|
|
||||||
create_time = _context.clock().now();
|
|
||||||
magnet_url = _magnet;
|
|
||||||
String str = magnet_url.toExternalForm();
|
|
||||||
int pos = str.indexOf( '?' );
|
|
||||||
if ( pos != -1 ) {
|
|
||||||
str = str.substring( pos+1 );
|
|
||||||
}
|
|
||||||
String[] args = str.split( "&" );
|
|
||||||
Map<String,String> arg_map = new HashMap<String,String>();
|
|
||||||
for ( String arg: args ) {
|
|
||||||
String[] bits = arg.split( "=" );
|
|
||||||
if ( bits.length == 2 ) {
|
|
||||||
try {
|
|
||||||
String lhs = bits[0].trim().toLowerCase( Locale.US );
|
|
||||||
String rhs = URLDecoder.decode( bits[1].trim(), "UTF-8");
|
|
||||||
if ( lhs.equals( "xt" )) {
|
|
||||||
if ( rhs.toLowerCase( Locale.US ).startsWith( "urn:btih:" )) {
|
|
||||||
arg_map.put( lhs, rhs );
|
|
||||||
} else {
|
|
||||||
String existing = arg_map.get( "xt" );
|
|
||||||
if ( existing == null ||
|
|
||||||
( !existing.toLowerCase( Locale.US ).startsWith( "urn:btih:" ) && rhs.startsWith( "urn:sha1:" ))) {
|
|
||||||
arg_map.put( lhs, rhs );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
arg_map.put( lhs, rhs );
|
|
||||||
}
|
|
||||||
} catch( Throwable e ) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash = new byte[0];
|
|
||||||
String hash_str = arg_map.get( "xt" );
|
|
||||||
if ( hash_str != null ) {
|
|
||||||
hash_str = hash_str.toLowerCase( Locale.US );
|
|
||||||
if ( hash_str.startsWith( "urn:btih:" ) || hash_str.startsWith( "urn:sha1" )) {
|
|
||||||
hash = UrlUtils.decodeSHA1Hash( hash_str.substring( 9 ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name = arg_map.get( "dn" );
|
|
||||||
if ( name == null ) {
|
|
||||||
if ( friendlyName != null ) {
|
|
||||||
name = friendlyName;
|
|
||||||
} else if ( hash == null ) {
|
|
||||||
name = magnet_url.toExternalForm();
|
|
||||||
} else {
|
|
||||||
name = Base32.encode( hash );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name = "Magnet download for '" + name + "'";
|
|
||||||
getID( this, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
private long
|
|
||||||
getCreateTime()
|
|
||||||
{
|
|
||||||
return( create_time );
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL
|
|
||||||
getMagnetURL()
|
|
||||||
{
|
|
||||||
return( magnet_url );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean
|
|
||||||
isStub()
|
|
||||||
{
|
|
||||||
return( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Snark
|
|
||||||
destubbify()
|
|
||||||
throws DownloadException
|
|
||||||
{
|
|
||||||
throw( new DownloadException( "Not supported" ));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String
|
|
||||||
getName()
|
|
||||||
{
|
|
||||||
return( name );
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[]
|
|
||||||
getInfoHash()
|
|
||||||
{
|
|
||||||
return( hash );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MetaInfo
|
|
||||||
getTorrent()
|
|
||||||
{
|
|
||||||
return( null );
|
|
||||||
}
|
|
||||||
|
|
||||||
public long
|
|
||||||
getTorrentSize()
|
|
||||||
{
|
|
||||||
return( 16*1024 ); // dont know the size
|
|
||||||
}
|
|
||||||
|
|
||||||
public String
|
|
||||||
getSavePath()
|
|
||||||
{
|
|
||||||
return( temp_dir );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void
|
|
||||||
setError(
|
|
||||||
Throwable e )
|
|
||||||
{
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Throwable
|
|
||||||
getError()
|
|
||||||
{
|
|
||||||
return( error );
|
|
||||||
}
|
|
||||||
|
|
||||||
public SnarkFile[]
|
|
||||||
getStubFiles()
|
|
||||||
{
|
|
||||||
return( new SnarkFile[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long
|
|
||||||
getLongAttribute(
|
|
||||||
TorrentAttribute attribute )
|
|
||||||
{
|
|
||||||
Long l = attributes.get( attribute );
|
|
||||||
return( l==null?0:l );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
setLongAttribute(
|
|
||||||
TorrentAttribute attribute,
|
|
||||||
long value)
|
|
||||||
{
|
|
||||||
attributes.put( attribute, value );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void
|
|
||||||
remove()
|
|
||||||
throws DownloadException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
****/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from Vuze UrlUtils.java
|
* Copied from Vuze UrlUtils.java
|
||||||
*/
|
*/
|
||||||
@ -5644,6 +5394,16 @@ XMWebUIPlugin {
|
|||||||
return _util.getString(s);
|
return _util.getString(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** translate */
|
||||||
|
private String _t(String s, Object o) {
|
||||||
|
return _util.getString(s, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** translate */
|
||||||
|
private String _t(String s, Object o, Object o2) {
|
||||||
|
return _util.getString(s, o, o2);
|
||||||
|
}
|
||||||
|
|
||||||
protected void log(String str) {
|
protected void log(String str) {
|
||||||
_log.debug(str);
|
_log.debug(str);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>i2psnark-rpc Plugin Version 0.1</h2>
|
<h2>i2psnark-rpc Plugin Version 0.3</h2>
|
||||||
<p>
|
<p>
|
||||||
This is a Transmission- and Vuze- compatible RPC server for i2psnark,
|
This is a Transmission- and Vuze- compatible RPC server for i2psnark,
|
||||||
with the Vuze-modified Transmission web UI.
|
with the Vuze-modified Transmission web UI.
|
||||||
|
@ -41,5 +41,11 @@ JSON:
|
|||||||
LGPLv2.1
|
LGPLv2.1
|
||||||
<br>
|
<br>
|
||||||
See <a href="licenses/JSON.txt">JSON.txt</a>
|
See <a href="licenses/JSON.txt">JSON.txt</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Transmission/Vuze web UI:
|
||||||
|
<br>
|
||||||
|
See <a href="licenses/LICENSE-GPLv2.txt">LICENSE-GPLv2.txt</a>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
<script type="text/javascript" src="./javascript/notifications.js"></script>
|
<script type="text/javascript" src="./javascript/notifications.js"></script>
|
||||||
<script type="text/javascript" src="./javascript/easyXDM-2.4.18.4.min.js"></script>
|
<script type="text/javascript" src="./javascript/easyXDM-2.4.18.4.min.js"></script>
|
||||||
<script type="text/javascript" src="./javascript/vuze.js"></script>
|
<script type="text/javascript" src="./javascript/vuze.js"></script>
|
||||||
<title>Vuze Remote</title>
|
<title>I2PSnark Remote</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="transmission_body">
|
<body id="transmission_body">
|
||||||
|
|
||||||
@ -62,7 +62,9 @@
|
|||||||
<div id="toolbar-remove" title="Remove Selected Torrents"></div>
|
<div id="toolbar-remove" title="Remove Selected Torrents"></div>
|
||||||
<div id="toolbar-start-all" title="Start All Torrents"></div>
|
<div id="toolbar-start-all" title="Start All Torrents"></div>
|
||||||
<div id="toolbar-pause-all" title="Pause All Torrents"></div>
|
<div id="toolbar-pause-all" title="Pause All Torrents"></div>
|
||||||
|
<!--
|
||||||
<div id="toolbar-search" title="Search"></div>
|
<div id="toolbar-search" title="Search"></div>
|
||||||
|
-->
|
||||||
|
|
||||||
<div id="toolbar-inspector" title="Toggle Inspector"></div>
|
<div id="toolbar-inspector" title="Toggle Inspector"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -92,7 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="torrent_logo">
|
<div id="torrent_logo">
|
||||||
<img src="style/transmission/images/graphics/vuze_remote_logo.png" alt="Vuze Remote">
|
<img src="style/i2p/images/graphics/i2plogo.png" alt="I2PSnark Remote">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="statusbar">
|
<div id="statusbar">
|
||||||
|
BIN
src/jsp/web/style/i2p/images/graphics/i2plogo.png
Normal file
BIN
src/jsp/web/style/i2p/images/graphics/i2plogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
Reference in New Issue
Block a user