7 Commits

Author SHA1 Message Date
zzz
401271e911 0.5
Remove bundled libjson-simple which conflicts with i2p.jar
Switch to use libjson-simple 2.x API
Requires I2P 0.9.47
2020-05-31 13:46:34 +00:00
zzz
4025f49581 Fixes for Sonarr, v0.4 2017-03-26 16:57:08 +00:00
zzz
4577202ba4 0.3 2017-03-23 13:19:10 +00:00
zzz
f48d7fc412 Set downloadDir for magnets
Hide search button, doesn't work
Replace logo
2017-03-22 14:08:22 +00:00
zzz
2eea36dc3a Implement torrent-add 2017-03-22 00:38:08 +00:00
zzz
878613c214 Changelog, tag 0.2 2017-03-21 12:01:49 +00:00
zzz
24a9d311f4 Implement percentDone
Return empty values for tag-uids and tag-get-list
Fix values for activityDateRelative and downloadedEver
Fix status flags
Return ?? for creator if not defined
Set size to 1 for magnet download
Implement startDate, requires 0.9.29-9
Set status correctly for a starting seeder and for magnet
Shorten peer address so it will fit in UI
Don't list primary tracker if we have a list, to not dup
Add license description for Transmission UI
Don't build update version of plugin
Prep for 0.2
2017-03-20 22:11:07 +00:00
20 changed files with 679 additions and 1628 deletions

View File

@ -1,2 +1,31 @@
* 2020-05-31 0.5
- Remove bundled libjson-simple which conflicts with i2p.jar
- Switch to use libjson-simple 2.x API in i2p.jar
- Requires I2P 0.9.47
* 2017-03-26 0.4
- Fixes for Sonarr
* 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
- Initial version
- Initial version, requires 0.9.29-8

View File

@ -18,6 +18,9 @@ Transmission:
Copyright (c) Transmission authors and contributors
See licenses/Transmission.txt
Transmission/Vuze web UI:
See licenses/LICENSE-GPLv2.txt
JSON:
LGPLv2.1
See licenses/JSON.txt

View File

@ -11,15 +11,12 @@
<target name="plugin" depends="war">
<!-- get version number -->
<buildnumber file="scripts/build.number" />
<property name="release.number" value="0.1" />
<property name="release.number" value="0.5" />
<!-- 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="README.txt" 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">
<arg value="version=${release.number}-b${build.number}" />
</exec>
@ -35,13 +32,7 @@
</condition>
</fail>
<!-- 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" >
<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 -->
<!-- make the install su3 -->
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="version=${release.number}-b${build.number}" />

View File

@ -1,6 +1,6 @@
name=i2psnark-rpc
signer=zzz-plugin@mail.i2p
consoleLinkName=Transmission
consoleLinkName=I2PSnark-Remote
consoleLinkURL=/transmission/web/
description=RPC and Web UI for i2psnark
author=zzz@mail.i2p
@ -9,4 +9,4 @@ websiteURL=http://zzz.i2p/forums/16
license=GPLv2
min-java-version=1.7
min-jetty-version=9
min-i2p-version=0.9.29
min-i2p-version=0.9.47

View File

@ -1,205 +0,0 @@
/*
* $Id: ItemList.java,v 1.2 2009-03-15 22:12:18 parg Exp $
* Created on 2006-3-24
*/
package org.json.simple;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
/**
* <20><><EFBFBD><EFBFBD><EFBFBD>÷ָ<C3B7><D6B8><EFBFBD><EFBFBD>ֿ<EFBFBD><D6BF><EFBFBD>һ<EFBFBD><D2BB>item.<2E>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>item.ÿ<><C3BF>item<65><6D><EFBFBD>߲<EFBFBD><DFB2><EFBFBD><EFBFBD>ǿհ׷<D5B0>.
* <20><><EFBFBD>
* |a:b:c| => |a|,|b|,|c|
* |:| => ||,||
* |a:| => |a|,||
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class ItemList {
private final static String sp=",";
List<String> items=new ArrayList<String>();
public ItemList(){}
/**
*
* @param s <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*/
public ItemList(String s){
this.split(s,sp,items);
}
/**
*
* @param s <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
* @param sp <20>ָ<EFBFBD><D6B8><EFBFBD>
*/
//public ItemList(String s,String sp){
// this.sp=s;
// this.split(s,sp,items);
//}
/**
*
* @param s
* @param sp
* @param isMultiToken sp<73>Ƿ<EFBFBD>Ϊ<EFBFBD><CEAA>ָ<EFBFBD><D6B8><EFBFBD>
*/
public ItemList(String s,String sp,boolean isMultiToken){
split(s,sp,items,isMultiToken);
}
public List<String> getItems(){
return this.items;
}
public String[] getArray(){
return (String[])this.items.toArray(new String[items.size()]);
}
public void split(String s,String sp,List<String> append,boolean isMultiToken){
if(s==null || sp==null)
return;
if(isMultiToken){
StringTokenizer tokens=new StringTokenizer(s,sp);
while(tokens.hasMoreTokens()){
append.add(tokens.nextToken().trim());
}
}
else{
this.split(s,sp,append);
}
}
public void split(String s,String sp,List<String> append){
if(s==null || sp==null)
return;
int pos=0;
int prevPos=0;
do{
prevPos=pos;
pos=s.indexOf(sp,pos);
if(pos==-1)
break;
append.add(s.substring(prevPos,pos).trim());
pos+=sp.length();
}while(pos!=-1);
append.add(s.substring(prevPos).trim());
}
/**
* <20><><EFBFBD>÷ָ<C3B7><D6B8><EFBFBD>.
* @param sp <20>ָ<EFBFBD><D6B8><EFBFBD>
*/
//public void setSP(String sp){
// this.sp=sp;
//}
/**
* <20><><EFBFBD><EFBFBD><EBB5A5>item.
* @param i <20><><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>(֮ǰ)
* @param item
*/
public void add(int i,String item){
if(item==null)
return;
items.add(i,item.trim());
}
/**
* <20><><EFBFBD><EFBFBD><EBB5A5>item.
* @param item
*/
public void add(String item){
if(item==null)
return;
items.add(item.trim());
}
/**
* <20><>һ<EFBFBD><D2BB>item.
* @param list <20><><EFBFBD><EFBFBD><EFBFBD>list
*/
public void addAll(ItemList list){
items.addAll(list.items);
}
/**
* <20><>һ<EFBFBD><D2BB>item.
* @param s <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*/
public void addAll(String s){
this.split(s,sp,items);
}
/**
* <20><>һ<EFBFBD><D2BB>item.
* @param s <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
* @param sp <20>ָ<EFBFBD><D6B8><EFBFBD>
*/
public void addAll(String s,String sp){
this.split(s,sp,items);
}
public void addAll(String s,String sp,boolean isMultiToken){
this.split(s,sp,items,isMultiToken);
}
/**
* <20><>õ<EFBFBD>i<EFBFBD><69>item. 0-based.
* @param i
* @return
*/
public String get(int i){
return (String)items.get(i);
}
/**
* <20><><EFBFBD>item<65><6D>.
* @return
*/
public int size(){
return items.size();
}
/**
* <20>÷ָ<C3B7><D6B8><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>ı<EFBFBD>ʾ.
*/
public String toString(){
return toString(sp);
}
/**
* <20>÷ָ<C3B7><D6B8><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>ı<EFBFBD>ʾ.
* @param sp <20><><EFBFBD><EFBFBD>ø÷ָ<C3B7><D6B8><EFBFBD><EFBFBD>ָ<EFBFBD>.
* @return
*/
public String toString(String sp){
StringBuilder sb=new StringBuilder();
for(int i=0;i<items.size();i++){
if(i==0)
sb.append(items.get(i));
else{
sb.append(sp);
sb.append(items.get(i));
}
}
return sb.toString();
}
/**
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>item.
*/
public void clear(){
items.clear();
}
/**
* <20><>λ.<2E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݣ<EFBFBD><DDA3><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><C4AC>ֵ.
*/
public void reset(){
//sp=",";
items.clear();
}
}

View File

@ -1,72 +0,0 @@
/*
* $Id: JSONArray.java,v 1.1 2007-06-05 00:43:56 tuxpaper Exp $
* Created on 2006-4-10
*/
package org.json.simple;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class JSONArray extends ArrayList<Object> {
public JSONArray() {
super();
}
public JSONArray(Collection<Object> arg0) {
super(arg0);
}
public JSONArray(int initialCapacity) {
super(initialCapacity);
}
public String toString(){
ItemList list=new ItemList();
Iterator<Object> iter=iterator();
while(iter.hasNext()){
Object value=iter.next();
if(value instanceof String){
list.add("\""+JSONObject.escape((String)value)+"\"");
}
else
list.add(String.valueOf(value));
}
return "["+list.toString()+"]";
}
public void toString( StringBuilder sb ){
sb.append( "[" );
Iterator<Object> iter=iterator();
boolean first = true;
while(iter.hasNext()){
if ( first ){
first = false;
}else{
sb.append( "," );
}
Object value=iter.next();
if(value instanceof String){
sb.append( "\"" );
JSONObject.escape(sb, (String)value);
sb.append( "\"");
}else if ( value instanceof JSONObject ){
((JSONObject)value).toString( sb );
}else if ( value instanceof JSONArray ){
((JSONArray)value).toString( sb );
}else{
sb.append(String.valueOf(value));
}
}
sb.append( "]" );
}
}

View File

@ -1,207 +0,0 @@
/*
* $Id: JSONObject.java,v 1.2 2008-08-07 01:18:54 parg Exp $
* Created on 2006-4-10
*/
package org.json.simple;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class JSONObject extends HashMap<String,Object>{
public JSONObject() {
super();
}
public JSONObject(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public JSONObject(int initialCapacity) {
super(initialCapacity);
}
public JSONObject(Map<String,Object> arg0) {
super(arg0);
}
public String toString(){
ItemList list=new ItemList();
Iterator<Map.Entry<String, Object>> iter=entrySet().iterator();
while(iter.hasNext()){
Map.Entry<String, Object> entry=iter.next();
list.add(toString(entry.getKey().toString(),entry.getValue()));
}
return "{"+list.toString()+"}";
}
public void toString( StringBuilder sb ){
sb.append( "{" );
Iterator iter=entrySet().iterator();
boolean first = true;
while(iter.hasNext()){
if ( first ){
first = false;
}else{
sb.append( "," );
}
Map.Entry entry=(Map.Entry)iter.next();
toString(sb, entry.getKey().toString(),entry.getValue());
}
sb.append( "}" );
}
public static String toString(String key,Object value){
StringBuilder sb=new StringBuilder();
sb.append("\"");
sb.append(escape(key));
sb.append("\":");
if(value==null){
sb.append("null");
return sb.toString();
}
if(value instanceof String){
sb.append("\"");
sb.append(escape((String)value));
sb.append("\"");
}
else
sb.append(value);
return sb.toString();
}
public static void toString(StringBuilder sb, String key,Object value){
sb.append("\"");
escape(sb,key);
sb.append("\":");
if(value==null){
sb.append("null");
return;
}
if(value instanceof String){
sb.append("\"");
escape(sb,(String)value);
sb.append("\"");
}else if ( value instanceof JSONObject ){
((JSONObject)value).toString( sb );
}else if ( value instanceof JSONArray ){
((JSONArray)value).toString( sb );
}else{
sb.append(String.valueOf( value ));
}
}
/**
* " => \" , \ => \\
* @param s
* @return
*/
public static String escape(String s){
if(s==null)
return null;
StringBuilder sb=new StringBuilder();
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
switch(ch){
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '/':
sb.append("\\/");
break;
default:
if(ch>='\u0000' && ch<='\u001F'){
String ss=Integer.toHexString(ch);
sb.append("\\u");
for(int k=0;k<4-ss.length();k++){
sb.append('0');
}
sb.append(ss.toUpperCase());
}
else{
sb.append(ch);
}
}
}//for
return sb.toString();
}
public static void escape(StringBuilder sb, String s){
if(s==null){
sb.append((String)null);
}else{
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
switch(ch){
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '/':
sb.append("\\/");
break;
default:
if(ch>='\u0000' && ch<='\u001F'){
String ss=Integer.toHexString(ch);
sb.append("\\u");
for(int k=0;k<4-ss.length();k++){
sb.append('0');
}
sb.append(ss.toUpperCase());
}
else{
sb.append(ch);
}
}
}//for
}
}
}

View File

@ -1,46 +0,0 @@
/*
* $Id: JSONValue.java,v 1.1 2007-06-05 00:43:56 tuxpaper Exp $
* Created on 2006-4-15
*/
package org.json.simple;
import java.io.Reader;
import java.io.StringReader;
import java.util.Map;
import org.json.simple.parser.JSONParser;
import org.klomp.snark.rpc.JSONUtils;
/**
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class JSONValue {
/**
* parse into java object from input source.
* @param in
* @return instance of : JSONObject,JSONArray,String,Boolean,Long,Double or null
*/
public static Object parse(Reader in){
try{
JSONParser parser=new JSONParser();
return parser.parse(in);
}
catch(Exception e){
return null;
}
}
public static Object parse(String s){
StringReader in=new StringReader(s);
return parse(in);
}
public static String toJSONString(Object value) {
if (value instanceof Map) {
return JSONUtils.encodeToJSON((Map) value);
}
return "";
}
}

View File

@ -1,190 +0,0 @@
/*
* $Id: JSONParser.java,v 1.2 2008-08-07 01:18:55 parg Exp $
* Created on 2006-4-15
*/
package org.json.simple.parser;
import java.io.Reader;
import java.util.Stack;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
/**
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class JSONParser {
public static final int S_INIT=0;
public static final int S_IN_FINISHED_VALUE=1;//string,number,boolean,null,object,array
public static final int S_IN_OBJECT=2;
public static final int S_IN_ARRAY=3;
public static final int S_PASSED_PAIR_KEY=4;
public static final int S_IN_ERROR=-1;
private int peekStatus(Stack statusStack){
if(statusStack.size()==0)
return -1;
Integer status=(Integer)statusStack.peek();
return status.intValue();
}
public Object parse(Reader in) throws Exception{
Stack statusStack=new Stack();
Stack valueStack=new Stack();
Yylex lexer=new Yylex(in);
Yytoken token=null;
int status=S_INIT;
try{
do{
token=lexer.yylex();
if(token==null)
token=new Yytoken(Yytoken.TYPE_EOF,null);
switch(status){
case S_INIT:
switch(token.type){
case Yytoken.TYPE_VALUE:
status=S_IN_FINISHED_VALUE;
statusStack.push(new Integer(status));
valueStack.push(token.value);
break;
case Yytoken.TYPE_LEFT_BRACE:
status=S_IN_OBJECT;
statusStack.push(new Integer(status));
valueStack.push(new JSONObject());
break;
case Yytoken.TYPE_LEFT_SQUARE:
status=S_IN_ARRAY;
statusStack.push(new Integer(status));
valueStack.push(new JSONArray());
break;
default:
status=S_IN_ERROR;
}//inner switch
break;
case S_IN_FINISHED_VALUE:
if(token.type==Yytoken.TYPE_EOF)
return valueStack.pop();
else
return null;
case S_IN_OBJECT:
switch(token.type){
case Yytoken.TYPE_COMMA:
break;
case Yytoken.TYPE_VALUE:
if(token.value instanceof String){
String key=(String)token.value;
valueStack.push(key);
status=S_PASSED_PAIR_KEY;
statusStack.push(new Integer(status));
}
else{
status=S_IN_ERROR;
}
break;
case Yytoken.TYPE_RIGHT_BRACE:
if(valueStack.size()>1){
statusStack.pop();
JSONObject map = (JSONObject)valueStack.pop();
status=peekStatus(statusStack);
}
else{
status=S_IN_FINISHED_VALUE;
}
break;
default:
status=S_IN_ERROR;
break;
}//inner switch
break;
case S_PASSED_PAIR_KEY:
switch(token.type){
case Yytoken.TYPE_COLON:
break;
case Yytoken.TYPE_VALUE:
statusStack.pop();
String key=(String)valueStack.pop();
JSONObject parent=(JSONObject)valueStack.peek();
parent.put(key,token.value);
status=peekStatus(statusStack);
break;
case Yytoken.TYPE_LEFT_SQUARE:
statusStack.pop();
key=(String)valueStack.pop();
parent=(JSONObject)valueStack.peek();
JSONArray newArray=new JSONArray();
parent.put(key,newArray);
status=S_IN_ARRAY;
statusStack.push(new Integer(status));
valueStack.push(newArray);
break;
case Yytoken.TYPE_LEFT_BRACE:
statusStack.pop();
key=(String)valueStack.pop();
parent=(JSONObject)valueStack.peek();
JSONObject newObject=new JSONObject();
parent.put(key,newObject);
status=S_IN_OBJECT;
statusStack.push(new Integer(status));
valueStack.push(newObject);
break;
default:
status=S_IN_ERROR;
}
break;
case S_IN_ARRAY:
switch(token.type){
case Yytoken.TYPE_COMMA:
break;
case Yytoken.TYPE_VALUE:
JSONArray val=(JSONArray)valueStack.peek();
val.add(token.value);
break;
case Yytoken.TYPE_RIGHT_SQUARE:
if(valueStack.size()>1){
statusStack.pop();
valueStack.pop();
status=peekStatus(statusStack);
}
else{
status=S_IN_FINISHED_VALUE;
}
break;
case Yytoken.TYPE_LEFT_BRACE:
val=(JSONArray)valueStack.peek();
JSONObject newObject=new JSONObject();
val.add(newObject);
status=S_IN_OBJECT;
statusStack.push(new Integer(status));
valueStack.push(newObject);
break;
case Yytoken.TYPE_LEFT_SQUARE:
val=(JSONArray)valueStack.peek();
JSONArray newArray=new JSONArray();
val.add(newArray);
status=S_IN_ARRAY;
statusStack.push(new Integer(status));
valueStack.push(newArray);
break;
default:
status=S_IN_ERROR;
}//inner switch
break;
case S_IN_ERROR:
return null;
}//switch
if(status==S_IN_ERROR)
return null;
}while(token.type!=Yytoken.TYPE_EOF);
}
catch(Exception e){
throw e;
}
return null;
}
}

View File

@ -1,428 +0,0 @@
package org.json.simple.parser;
class Yylex {
private final static int YY_BUFFER_SIZE = 512;
private final static int YY_F = -1;
private final static int YY_NO_STATE = -1;
private final static int YY_NOT_ACCEPT = 0;
//private final static int YY_START = 1;
private final static int YY_END = 2;
private final static int YY_NO_ANCHOR = 4;
private final static int YY_BOL = 65536;
private final static int YY_EOF = 65537;
private StringBuffer sb=new StringBuffer();
private java.io.BufferedReader yy_reader;
private int yy_buffer_index;
private int yy_buffer_read;
private int yy_buffer_start;
private int yy_buffer_end;
private char yy_buffer[];
private boolean yy_at_bol;
private int yy_lexical_state;
Yylex (java.io.Reader reader) {
this ();
if (null == reader) {
throw (new Error("Error: Bad input stream initializer."));
}
yy_reader = new java.io.BufferedReader(reader);
}
Yylex (java.io.InputStream instream) {
this ();
if (null == instream) {
throw (new Error("Error: Bad input stream initializer."));
}
yy_reader = new java.io.BufferedReader(new java.io.InputStreamReader(instream));
}
private Yylex () {
yy_buffer = new char[YY_BUFFER_SIZE];
yy_buffer_read = 0;
yy_buffer_index = 0;
yy_buffer_start = 0;
yy_buffer_end = 0;
yy_at_bol = true;
yy_lexical_state = YYINITIAL;
}
//private boolean yy_eof_done = false;
private static final int YYINITIAL = 0;
private static final int STRING_BEGIN = 1;
private static final int yy_state_dtrans[] = {
0,
39
};
private void yybegin (int state) {
yy_lexical_state = state;
}
private int yy_advance ()
throws java.io.IOException {
int next_read;
int i;
int j;
if (yy_buffer_index < yy_buffer_read) {
return yy_buffer[yy_buffer_index++];
}
if (0 != yy_buffer_start) {
i = yy_buffer_start;
j = 0;
while (i < yy_buffer_read) {
yy_buffer[j] = yy_buffer[i];
++i;
++j;
}
yy_buffer_end = yy_buffer_end - yy_buffer_start;
yy_buffer_start = 0;
yy_buffer_read = j;
yy_buffer_index = j;
next_read = yy_reader.read(yy_buffer,
yy_buffer_read,
yy_buffer.length - yy_buffer_read);
if (-1 == next_read) {
return YY_EOF;
}
yy_buffer_read = yy_buffer_read + next_read;
}
while (yy_buffer_index >= yy_buffer_read) {
if (yy_buffer_index >= yy_buffer.length) {
yy_buffer = yy_double(yy_buffer);
}
next_read = yy_reader.read(yy_buffer,
yy_buffer_read,
yy_buffer.length - yy_buffer_read);
if (-1 == next_read) {
return YY_EOF;
}
yy_buffer_read = yy_buffer_read + next_read;
}
return yy_buffer[yy_buffer_index++];
}
private void yy_move_end () {
if (yy_buffer_end > yy_buffer_start &&
'\n' == yy_buffer[yy_buffer_end-1])
yy_buffer_end--;
if (yy_buffer_end > yy_buffer_start &&
'\r' == yy_buffer[yy_buffer_end-1])
yy_buffer_end--;
}
//private boolean yy_last_was_cr=false;
private void yy_mark_start () {
yy_buffer_start = yy_buffer_index;
}
private void yy_mark_end () {
yy_buffer_end = yy_buffer_index;
}
private void yy_to_mark () {
yy_buffer_index = yy_buffer_end;
yy_at_bol = (yy_buffer_end > yy_buffer_start) &&
('\r' == yy_buffer[yy_buffer_end-1] ||
'\n' == yy_buffer[yy_buffer_end-1] ||
2028/*LS*/ == yy_buffer[yy_buffer_end-1] ||
2029/*PS*/ == yy_buffer[yy_buffer_end-1]);
}
private java.lang.String yytext () {
return (new java.lang.String(yy_buffer,
yy_buffer_start,
yy_buffer_end - yy_buffer_start));
}
//private int yylength () {
// return yy_buffer_end - yy_buffer_start;
//}
private char[] yy_double (char buf[]) {
int i;
char newbuf[];
newbuf = new char[2*buf.length];
for (i = 0; i < buf.length; ++i) {
newbuf[i] = buf[i];
}
return newbuf;
}
private static final int YY_E_INTERNAL = 0;
//private final int YY_E_MATCH = 1;
private java.lang.String yy_error_string[] = {
"Error: Internal error.\n",
"Error: Unmatched input.\n"
};
private void yy_error (int code,boolean fatal) {
java.lang.System.out.print(yy_error_string[code]);
java.lang.System.out.flush();
if (fatal) {
throw new Error("Fatal Error.\n");
}
}
private static int[][] unpackFromString(int size1, int size2, String st) {
int colonIndex = -1;
String lengthString;
int sequenceLength = 0;
int sequenceInteger = 0;
int commaIndex;
String workString;
int res[][] = new int[size1][size2];
for (int i= 0; i < size1; i++) {
for (int j= 0; j < size2; j++) {
if (sequenceLength != 0) {
res[i][j] = sequenceInteger;
sequenceLength--;
continue;
}
commaIndex = st.indexOf(',');
workString = (commaIndex==-1) ? st :
st.substring(0, commaIndex);
st = st.substring(commaIndex+1);
colonIndex = workString.indexOf(':');
if (colonIndex == -1) {
res[i][j]=Integer.parseInt(workString);
continue;
}
lengthString =
workString.substring(colonIndex+1);
sequenceLength=Integer.parseInt(lengthString);
workString=workString.substring(0,colonIndex);
sequenceInteger=Integer.parseInt(workString);
res[i][j] = sequenceInteger;
sequenceLength--;
}
}
return res;
}
private static final int yy_acpt[] = {
/* 0 */ YY_NOT_ACCEPT,
/* 1 */ YY_NO_ANCHOR,
/* 2 */ YY_NO_ANCHOR,
/* 3 */ YY_NO_ANCHOR,
/* 4 */ YY_NO_ANCHOR,
/* 5 */ YY_NO_ANCHOR,
/* 6 */ YY_NO_ANCHOR,
/* 7 */ YY_NO_ANCHOR,
/* 8 */ YY_NO_ANCHOR,
/* 9 */ YY_NO_ANCHOR,
/* 10 */ YY_NO_ANCHOR,
/* 11 */ YY_NO_ANCHOR,
/* 12 */ YY_NO_ANCHOR,
/* 13 */ YY_NO_ANCHOR,
/* 14 */ YY_NO_ANCHOR,
/* 15 */ YY_NO_ANCHOR,
/* 16 */ YY_NO_ANCHOR,
/* 17 */ YY_NO_ANCHOR,
/* 18 */ YY_NO_ANCHOR,
/* 19 */ YY_NO_ANCHOR,
/* 20 */ YY_NO_ANCHOR,
/* 21 */ YY_NO_ANCHOR,
/* 22 */ YY_NO_ANCHOR,
/* 23 */ YY_NO_ANCHOR,
/* 24 */ YY_NO_ANCHOR,
/* 25 */ YY_NOT_ACCEPT,
/* 26 */ YY_NO_ANCHOR,
/* 27 */ YY_NO_ANCHOR,
/* 28 */ YY_NOT_ACCEPT,
/* 29 */ YY_NOT_ACCEPT,
/* 30 */ YY_NOT_ACCEPT,
/* 31 */ YY_NOT_ACCEPT,
/* 32 */ YY_NOT_ACCEPT,
/* 33 */ YY_NOT_ACCEPT,
/* 34 */ YY_NOT_ACCEPT,
/* 35 */ YY_NOT_ACCEPT,
/* 36 */ YY_NOT_ACCEPT,
/* 37 */ YY_NOT_ACCEPT,
/* 38 */ YY_NOT_ACCEPT,
/* 39 */ YY_NOT_ACCEPT,
/* 40 */ YY_NOT_ACCEPT,
/* 41 */ YY_NOT_ACCEPT,
/* 42 */ YY_NOT_ACCEPT,
/* 43 */ YY_NOT_ACCEPT,
/* 44 */ YY_NOT_ACCEPT
};
private static final int yy_cmap[] = unpackFromString(1,65538,
"11:8,27:2,28,11,27,28,11:18,27,11,2,11:8,16,25,12,14,3,13:10,26,11:6,10:4,1" +
"5,10,11:20,23,1,24,11:3,18,4,10:2,17,5,11:5,19,11,6,11:3,7,20,8,9,11:5,21,1" +
"1,22,11:65410,0:2")[0];
private static final int yy_rmap[] = unpackFromString(1,45,
"0,1:2,2,1:7,3,1:2,4,1:10,5,6,1,7,8,9,10,11,12,13,14,15,16,6,17,18,19,20,21," +
"22")[0];
private static final int yy_nxt[][] = unpackFromString(23,29,
"1,-1,2,-1:2,25,28,-1,29,-1:3,30,3,-1:7,4,5,6,7,8,9,10:2,-1:42,3,33,34,-1,34" +
",-1:24,11,-1,34,-1,34,-1:12,16,17,18,19,20,21,22,23,40,-1:37,31,-1:23,26,-1" +
":24,42,-1:26,32,-1:34,3,-1:34,35,-1:18,37,-1:32,11,-1:27,38,26,-1:2,38,-1:3" +
"2,37,-1:27,12,-1:26,13,-1:11,1,14,15,27:25,-1:5,44:2,-1:4,44,-1:2,44,-1,44," +
"-1,44:2,-1:14,24:2,-1:4,24,-1:2,24,-1,24,-1,24:2,-1:29,36,-1:13,41:2,-1:4,4" +
"1,-1:2,41,-1,41,-1,41:2,-1:14,43:2,-1:4,43,-1:2,43,-1,43,-1,43:2,-1:10");
public Yytoken yylex ()
throws java.io.IOException {
int yy_lookahead;
int yy_anchor = YY_NO_ANCHOR;
int yy_state = yy_state_dtrans[yy_lexical_state];
int yy_next_state = YY_NO_STATE;
int yy_last_accept_state = YY_NO_STATE;
boolean yy_initial = true;
int yy_this_accept;
yy_mark_start();
yy_this_accept = yy_acpt[yy_state];
if (YY_NOT_ACCEPT != yy_this_accept) {
yy_last_accept_state = yy_state;
yy_mark_end();
}
while (true) {
if (yy_initial && yy_at_bol) yy_lookahead = YY_BOL;
else yy_lookahead = yy_advance();
yy_next_state = YY_F;
yy_next_state = yy_nxt[yy_rmap[yy_state]][yy_cmap[yy_lookahead]];
if (YY_EOF == yy_lookahead && true == yy_initial) {
return null;
}
if (YY_F != yy_next_state) {
yy_state = yy_next_state;
yy_initial = false;
yy_this_accept = yy_acpt[yy_state];
if (YY_NOT_ACCEPT != yy_this_accept) {
yy_last_accept_state = yy_state;
yy_mark_end();
}
}
else {
if (YY_NO_STATE == yy_last_accept_state) {
throw (new Error("Lexical Error: Unmatched Input."));
}
else {
yy_anchor = yy_acpt[yy_last_accept_state];
if (0 != (YY_END & yy_anchor)) {
yy_move_end();
}
yy_to_mark();
switch (yy_last_accept_state) {
case 1:
case -2:
break;
case 2:
{ sb.delete(0,sb.length());yybegin(STRING_BEGIN);}
case -3:
break;
case 3:
{ Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);}
case -4:
break;
case 4:
{ return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null);}
case -5:
break;
case 5:
{ return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null);}
case -6:
break;
case 6:
{ return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null);}
case -7:
break;
case 7:
{ return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null);}
case -8:
break;
case 8:
{ return new Yytoken(Yytoken.TYPE_COMMA,null);}
case -9:
break;
case 9:
{ return new Yytoken(Yytoken.TYPE_COLON,null);}
case -10:
break;
case 10:
{}
case -11:
break;
case 11:
{ Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);}
case -12:
break;
case 12:
{ return new Yytoken(Yytoken.TYPE_VALUE,null);}
case -13:
break;
case 13:
{ Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);}
case -14:
break;
case 14:
{ sb.append(yytext());}
case -15:
break;
case 15:
{ yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE,sb.toString());}
case -16:
break;
case 16:
{sb.append('\\');}
case -17:
break;
case 17:
{sb.append('"');}
case -18:
break;
case 18:
{sb.append('/');}
case -19:
break;
case 19:
{sb.append('\b');}
case -20:
break;
case 20:
{sb.append('\f');}
case -21:
break;
case 21:
{sb.append('\n');}
case -22:
break;
case 22:
{sb.append('\r');}
case -23:
break;
case 23:
{sb.append('\t');}
case -24:
break;
case 24:
{ int ch=Integer.parseInt(yytext().substring(2),16);
sb.append((char)ch);
}
case -25:
break;
case 26:
{ Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE,val);}
case -26:
break;
case 27:
{ sb.append(yytext());}
case -27:
break;
default:
yy_error(YY_E_INTERNAL,false);
case -1:
}
yy_initial = true;
yy_state = yy_state_dtrans[yy_lexical_state];
yy_next_state = YY_NO_STATE;
yy_last_accept_state = YY_NO_STATE;
yy_mark_start();
yy_this_accept = yy_acpt[yy_state];
if (YY_NOT_ACCEPT != yy_this_accept) {
yy_last_accept_state = yy_state;
yy_mark_end();
}
}
}
}
}
}

View File

@ -1,31 +0,0 @@
/*
* $Id: Yytoken.java,v 1.1 2007-06-05 00:43:56 tuxpaper Exp $
* Created on 2006-4-15
*/
package org.json.simple.parser;
/**
* @author FangYidong<fangyidong@yahoo.com.cn>
*/
public class Yytoken {
public static final int TYPE_VALUE=0;//JSON primitive value: string,number,boolean,null
public static final int TYPE_LEFT_BRACE=1;
public static final int TYPE_RIGHT_BRACE=2;
public static final int TYPE_LEFT_SQUARE=3;
public static final int TYPE_RIGHT_SQUARE=4;
public static final int TYPE_COMMA=5;
public static final int TYPE_COLON=6;
public static final int TYPE_EOF=-1;//end of file
public int type=0;
public Object value=null;
public Yytoken(int type,Object value){
this.type=type;
this.value=value;
}
public String toString(){
return String.valueOf(type+"=>|"+value+"|");
}
}

View 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);
}
}

View File

@ -20,9 +20,9 @@ package org.klomp.snark.rpc;
import java.io.UnsupportedEncodingException;
import java.util.*;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.JsonArray;
import org.json.simple.JsonObject;
import org.json.simple.Jsoner;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
@ -47,7 +47,7 @@ public class JSONUtils
*/
public static Map decodeJSON(String json) {
try {
Object object = JSONValue.parse(json);
Object object = Jsoner.deserialize(json);
if (object instanceof Map) {
return (Map) object;
}
@ -62,7 +62,7 @@ public class JSONUtils
}
/**
* encodes a map into a JSONObject.
* encodes a map into a JsonObject.
* <P>
* It's recommended that you use {@link #encodeToJSON(Map)} instead
*
@ -71,8 +71,8 @@ public class JSONUtils
*
* @since 3.0.1.5
*/
public static JSONObject encodeToJSONObject(Map map) {
JSONObject newMap = new JSONObject((int)(map.size()*1.5));
public static JsonObject encodeToJSONObject(Map map) {
JsonObject newMap = new JsonObject();
for (Map.Entry<String, Object> entry: ((Map<String,Object>)map).entrySet()){
String key = entry.getKey();
@ -99,14 +99,12 @@ public class JSONUtils
* @since 3.0.1.5
*/
public static String encodeToJSON(Map map) {
JSONObject jobj = encodeToJSONObject(map);
StringBuilder sb = new StringBuilder(8192);
jobj.toString( sb );
return( sb.toString());
JsonObject jobj = encodeToJSONObject(map);
return jobj.toJson();
}
public static String encodeToJSON(Collection list) {
return encodeToJSONArray(list).toString();
return encodeToJSONArray(list).toJson();
}
private static Object coerce(Object value) {
@ -153,8 +151,8 @@ public class JSONUtils
*
* @since 3.0.1.5
*/
private static JSONArray encodeToJSONArray(Collection list) {
JSONArray newList = new JSONArray(list.size());
private static JsonArray encodeToJSONArray(Collection list) {
JsonArray newList = new JsonArray();
for ( Object value: list ){
newList.add(coerce(value));
}

View File

@ -24,6 +24,13 @@ public class RPCServlet extends HttpServlet {
private SnarkManager _manager;
private XMWebUIPlugin _plugin;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Sonarr does a GET to test,
// pass it through so it will get the 409 response
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
XMWebUIPlugin plugin;

View File

@ -1,5 +1,10 @@
package org.klomp.snark.rpc;
import java.io.File;
import java.io.IOException;
import net.i2p.data.DataHelper;
import org.klomp.snark.PeerID;
/**
@ -84,4 +89,69 @@ class UIUtil {
}
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)) + "&hellip;";
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("&", "&amp;").replace(" ", "%20")
.replace("<", "%3C").replace(">", "%3E")
.replace("[", "%5B").replace("]", "%5D");
}
}

View File

@ -19,6 +19,7 @@
*/
package org.klomp.snark.rpc;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -31,6 +32,7 @@ import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Collection;
import java.util.Collections;
@ -40,6 +42,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@ -61,11 +64,13 @@ import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.servlet.RequestWrapper;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.klomp.snark.BitField;
import org.klomp.snark.I2PSnarkUtil;
import org.klomp.snark.MagnetURI;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Peer;
import org.klomp.snark.PeerID;
@ -75,8 +80,7 @@ import org.klomp.snark.Storage;
import org.klomp.snark.TrackerClient;
import org.klomp.snark.bencode.BEncoder;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.JsonObject;
@SuppressWarnings({
"unchecked",
@ -420,7 +424,7 @@ XMWebUIPlugin {
generateSupport(HttpServletRequest request, HttpServletResponse response) throws IOException {
boolean logit = trace_param;
if (logit) {
log("-> " + request.getServletPath());
log("-> " + request.getMethod() + ' ' + request.getServletPath());
String qs = request.getQueryString();
if (qs != null)
log( "-> query: " + qs);
@ -435,10 +439,6 @@ XMWebUIPlugin {
response.setStatus( 415 );
return true;
}
if (!request.getMethod().equals("POST")) {
response.setStatus( 405 );
return true;
}
try {
String session_id = getSessionID( request );
// Set cookie just in case client is looking for one..
@ -453,6 +453,12 @@ XMWebUIPlugin {
response.getOutputStream().write("You_didn_t_set_the_X-Transmission-Session-Id".getBytes());
return true;
}
if (!request.getMethod().equals("POST")) {
// Sonarr does a GET for testing and to get the 409, shouldn't go past here
response.setContentType("text/plain; charset=UTF-8");
response.setStatus(200);
return true;
}
String session_id_plus = session_id;
// XXX getHeaders() keys are lowercase.. this line always null?
String tid = request.getHeader( "X-XMRPC-Tunnel-ID" );
@ -833,13 +839,11 @@ XMWebUIPlugin {
method_Session_Get(args, result);
} else if ( method.equals( "session-stats" )) {
method_Session_Stats(args, result);
/****
} else if ( method.equals( "torrent-add" )) {
String agent = MapUtils.getMapString(request.getHeaders(), "User-Agent", "");
boolean xmlEscape = agent.startsWith("Mozilla/");
String agent = request.getHeader("User-Agent");
boolean xmlEscape = agent != null && agent.startsWith("Mozilla/");
method_Torrent_Add(args, result, xmlEscape);
// this is handled within the torrent-add method: save_core_state = true;
****/
} else if ( method.equals( "torrent-start-all" )) {
checkUpdatePermissions();
_manager.startAllTorrents();
@ -907,9 +911,11 @@ XMWebUIPlugin {
} else if ( method.equals( "torrent-rename-path" )) {
// RPC v15
method_Torrent_Rename_Path(args, result);
*/
} else if ( method.equals( "tags-get-list" )) {
// Vuze RPC v3
method_Tags_Get_List(args, result);
/*
} else if ( method.equals( "tags-lookup-start" )) {
method_Tags_Lookup_Start(args, result);
} else if ( method.equals( "tags-lookup-get-results" )) {
@ -1182,7 +1188,7 @@ XMWebUIPlugin {
List<Map> l_engines = new ArrayList<Map>();
result.put("engines", l_engines);
for (Engine engine : engines) {
JSONObject map = new JSONObject();
JsonObject map = new JsonObject();
l_engines.add(map);
map.put("name", engine.getName());
map.put("id", engine.getUID());
@ -1637,6 +1643,7 @@ XMWebUIPlugin {
}
map.put(id, o);
}
****/
private void method_Tags_Get_List(Map args, Map result) {
List fields = (List) args.get("fields");
@ -1647,6 +1654,7 @@ XMWebUIPlugin {
}
List<SortedMap<String, Object>> listTags =
new ArrayList<SortedMap<String,Object>>();
/****
TagManager tm = TagManagerFactory.getTagManager();
List<TagType> tagTypes = tm.getTagTypes();
for (TagType tagType : tagTypes) {
@ -1719,6 +1727,7 @@ XMWebUIPlugin {
listTags.add(map);
}
}
****/
String hc = Long.toHexString(longHashSimpleList(listTags));
result.put("tags-hc", hc);
String oldHC = MapUtils.getMapString(args, "tags-hc", null);
@ -1726,7 +1735,6 @@ XMWebUIPlugin {
result.put("tags", listTags);
}
}
****/
/*
This method tests how much free space is available in a
@ -1918,7 +1926,8 @@ XMWebUIPlugin {
result.put( "rpc-version-minimum", Long.valueOf(6)); // number the minimum RPC API version supported
result.put( "seedRatioLimit", Double.valueOf(100.0) ); // double the default seed ratio for torrents to use
result.put( "seedRatioLimited", Boolean.FALSE ); // boolean true if seedRatioLimit is honored by default
result.put( "version", CoreVersion.VERSION); // string
result.put( "version", "2.80"); // This must match the RPC API from the spec to make Sonarr happy
result.put( "i2p-version", CoreVersion.VERSION); // unused
result.put( "az-rpc-version", VUZE_RPC_VERSION);
result.put( "az-version", az_version ); // string
result.put( "az-mode", az_mode ); // string
@ -2911,7 +2920,6 @@ XMWebUIPlugin {
form of one of 3.3's tr_info objects with the
fields for id, name, and hashString.
*/
/****
private void
method_Torrent_Add(
final Map args,
@ -2923,46 +2931,41 @@ XMWebUIPlugin {
String metainfoString = (String) args.get("metainfo");
byte[] metainfoBytes = null;
if ( metainfoString != null ) {
metainfoBytes = Base64.decode( metainfoString.replaceAll("[\r\n]+", "") );
//metainfoBytes = Base64.decode( metainfoString );
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
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 ));
}
}
}
metainfoBytes = Base64.decode( metainfoString.replaceAll("[\r\n]+", ""), true );
if (metainfoBytes == null)
throw new TextualException("bad metainfo base64");
}
Snark torrent = null;
MetaInfo torrent = null;
Snark download = null;
String url = (String) args.get("filename");
final boolean add_stopped = getBoolean(args.get("paused"));
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
//getNumber(args.get("peer-limit"), 0);
// bandwidthPriority not used
//getNumber(args.get("bandwidthPriority"), TR_PRI_NORMAL);
/****
final DownloadWillBeAddedListener add_listener =
new DownloadWillBeAddedListener() {
public void initialised(Snark download) {
@ -3008,6 +3011,7 @@ XMWebUIPlugin {
DiskManagerFileInfo.PRIORITY_LOW);
}
}
// don't need priority-normal if they are normal by default.
// handle initial categories/tags
try {
@ -3034,164 +3038,52 @@ XMWebUIPlugin {
}
}
};
TorrentManager torrentManager = plugin_interface.getTorrentManager();
****/
boolean duplicate = false;
if ( metainfoBytes != null ) {
try {
torrent = torrentManager.createFromBEncodedData( metainfoBytes);
SnarkManager dm = _manager;
download = dm.getDownload( torrent );
duplicate = download != null;
} catch (Throwable e) {
e.printStackTrace();
//System.err.println("decode of " + new String(Base64.encode(metainfoBytes), "UTF8"));
throw (new IOException("torrent download failed: "
+ Debug.getNestedExceptionMessage(e)));
torrent = new MetaInfo(new ByteArrayInputStream(metainfoBytes));
download = _manager.getTorrentByInfoHash(torrent.getInfoHash());
duplicate = download != null;
if ( download == null ) {
boolean success = _manager.addTorrent( torrent, null, null, file_Download_dir, add_stopped );
if (success)
download = _manager.getTorrentByInfoHash(torrent.getInfoHash());
}
} else if (url == null) {
throw (new IOException("url missing"));
} else {
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);
if (hashFromMagnetURI != null) {
org.gudy.azureus2.plugins.download.SnarkManager dm = _manager;
download = dm.getDownload(hashFromMagnetURI);
download = _manager.getTorrentByInfoHash(hashFromMagnetURI);
duplicate = download != null;
}
if (download == null) {
URL torrent_url;
try {
torrent_url = new URL(url);
} catch (MalformedURLException mue) {
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();
}
}
// This code is copied from I2PSnarkServlet
if (url.startsWith("http://")) {
download = new FetchAndAdd(_context, _manager, url, file_Download_dir);
_manager.addDownloader(download);
} 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 = addTorrent( torrent, file_Download_dir, add_stopped, add_listener );
}
// download must be non-null here, either the new torrent or the old duplicate
Map<String, Object> torrent_details = new HashMap<String, Object>();
torrent_details.put("id", new Long(getID(download, true)));
torrent_details.put("name", xmlEscape ? escapeXML(download.getName()) : download.getName());
@ -3200,15 +3092,32 @@ XMWebUIPlugin {
result.put(duplicate ? "torrent-duplicate" : "torrent-added", torrent_details);
}
private static byte[] getHashFromMagnetURI(String magnetURI) {
Pattern patXT = Pattern.compile("xt=urn:(?:btih|sha1):([^&]+)");
Matcher matcher = patXT.matcher(magnetURI);
if (matcher.find()) {
return UrlUtils.decodeSHA1Hash(matcher.group(1));
private byte[] getHashFromMagnetURI(String magnetURI) {
try {
MagnetURI magnet = new MagnetURI(_util, magnetURI);
return magnet.getInfoHash();
} 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
method_Torrent_Get(
@ -3324,7 +3233,10 @@ XMWebUIPlugin {
// RPC v0
// activityDate | number | tr_stat
//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")) {
// RPC v0
// addedDate | number | tr_stat
@ -3360,10 +3272,13 @@ XMWebUIPlugin {
} else if (field.equals("creator")) {
// RPC v0
// creator | string | tr_info
if (t != null)
if (t != null) {
value = t.getCreatedBy();
else
value = "";
if (value == null)
value = "??";
} else {
value = "??";
}
} else if (field.equals("dateCreated")) {
// RPC v0
// dateCreated | number | tr_info
@ -3407,6 +3322,9 @@ XMWebUIPlugin {
// downloadDir | string | tr_torrent
if (storage != null) {
value = storage.getBase().getAbsolutePath();
} else {
//value = "TBD";
value = _manager.getDataDir().getAbsolutePath();
}
} else if (field.equals("downloadedEver")) {
// RPC v0
@ -3416,7 +3334,14 @@ XMWebUIPlugin {
* for this torrent. If you deleted the files and downloaded a second
* 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")
|| field.equals("speed-limit-down")) {
// RPC v5 (alternate is from 'set' prior to v5 -- added for rogue clients)
@ -3534,7 +3459,7 @@ XMWebUIPlugin {
if (needed >= 0)
value = needed;
else
value = 16384; // TODO
value = 1; // TODO
} else if (field.equals("magnetLink")) {
// TODO RPC v7
// magnetLink | number | n/a
@ -3611,15 +3536,21 @@ XMWebUIPlugin {
}
value = count;
}
/*
} else if (field.equals("percentDone")) {
// RPC v5
// percentDone | double | tr_stat
// 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.
// 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")) {
// RPC v5
value = torrentGet_pieces(download);
@ -3637,12 +3568,13 @@ XMWebUIPlugin {
value = 16384;
} else if (field.equals("priorities")) {
value = torrentGet_priorities(download);
/*
} else if (field.equals("queuePosition")) {
// RPC v14
// "queuePosition" | number position of this torrent in its queue [0...n)
/*
value = core_download.getPosition();
*/
value = 0;
} else if (field.equals("rateDownload")) {
// rateSnark (B/s) | number | tr_stat
value = download.getDownloadRate();
@ -3690,21 +3622,28 @@ XMWebUIPlugin {
* if only some of the torrent's files are wanted.
* [0...tr_info.totalSize]
**/
value = download.getTotalLength() - download.getSkippedLength();
value = Math.max(download.getTotalLength() - download.getSkippedLength(), 1L);
} else if (field.equals("startDate")) {
// When the torrent was last started.
//value = stats.getTimeStarted() / 1000;
value = 0;
try {
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")) {
if (download.isStarting())
value = TR_STATUS_DOWNLOAD_WAIT;
else if (download.isStopped())
if (download.isStarting()) {
if (download.getRemainingLength() == 0)
value = TR_STATUS_SEED_WAIT;
else
value = TR_STATUS_DOWNLOAD_WAIT;
} else if (download.isStopped())
value = TR_STATUS_STOPPED;
else if (download.isChecking())
value = TR_STATUS_CHECK;
else if (download.isAllocating())
value = TR_STATUS_DOWNLOAD;
else if (download.getRemainingLength() <= 0)
value = TR_STATUS_DOWNLOAD_WAIT;
else if (download.getRemainingLength() == 0)
value = TR_STATUS_SEED;
else
value = TR_STATUS_DOWNLOAD;
@ -3720,13 +3659,14 @@ XMWebUIPlugin {
boolean hack = agent != null && agent.contains("httpok"); // Torrnado
value = torrentGet_trackers(download, hack);
} else if (field.equals("totalSize")) {
value = download.getTotalLength();
value = Math.max(download.getTotalLength(), 1L);
} else if (field.equals("torrentFile")) {
// torrentFile | string | tr_info
// Path to torrent
value = download.getName();
} else if (field.equals("uploadedEver")) {
// uploadedEver | number | tr_stat
// we don't persist this, just give them what we have sent this time
value = download.getUploaded();
} else if (field.equals("uploadLimit") || field.equals("speed-limit-up")) {
// RPC v5 (alternate is from 'set' prior to v5 -- added for rogue clients)
@ -3870,8 +3810,8 @@ XMWebUIPlugin {
* }
* }
*/
/*
} else if (field.equals("tag-uids")) {
/*
// azRPC
List<Long> listTags = new ArrayList<Long>();
TagManager tm = TagManagerFactory.getTagManager();
@ -3892,6 +3832,7 @@ XMWebUIPlugin {
}
value = listTags;
*/
value = Collections.EMPTY_LIST;
} else {
if ( trace_param ) {
log("Unhandled get-torrent field: " + field);
@ -4176,7 +4117,9 @@ XMWebUIPlugin {
String name = t.getAnnounce();
if (name == null)
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("://")) {
name = "://" + name;
}
@ -4190,7 +4133,6 @@ XMWebUIPlugin {
map.put("tier", tier++);
trackers.add(map);
}
List<List<String>> alist = t.getAnnounceList();
if (alist != null && !alist.isEmpty()) {
for (List<String> alist2 : alist) {
for (String name2 : alist2) {
@ -4538,33 +4480,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
* to be considered stalled.
@ -4606,7 +4521,14 @@ XMWebUIPlugin {
boolean isDownloadingFrom = !peer.isChoked() && dlRate > 0;
boolean isUploadingTo = !peer.isChoking() && ulRate > 0;
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);
String client = UIUtil.getClientName(peer.getPeerID());
map.put("clientName", client);
@ -4630,14 +4552,14 @@ XMWebUIPlugin {
flagStr.append('D');
} else if (peer.isInteresting()) {
flagStr.append('d');
} else if (peer.isChoked()) {
} else if (!peer.isChoked()) {
flagStr.append('K');
}
if (isUploadingTo) {
flagStr.append('U');
} else if (peer.isInterested()) {
flagStr.append('u');
} else if (peer.isChoking()) {
} else if (!peer.isChoking()) {
flagStr.append('?');
}
flagStr.append('E');
@ -5425,176 +5347,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
*/
@ -5644,6 +5396,16 @@ XMWebUIPlugin {
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) {
_log.debug(str);
}

View File

@ -1,6 +1,6 @@
<html>
<body>
<h2>i2psnark-rpc Plugin Version 0.1</h2>
<h2>i2psnark-rpc Plugin Version 0.4</h2>
<p>
This is a Transmission- and Vuze- compatible RPC server for i2psnark,
with the Vuze-modified Transmission web UI.
@ -16,6 +16,11 @@ specify port 7657. For example, to list the torrents:
transmission-remote 7657 -l
</pre>
<p>
For <a href="https://sonarr.tv/">Sonarr</a>:
Settings -&gt; Download Client -&gt click "+" -&gt; click "Transmission" -&gt; change port to 7657 and click "Save"
<p>
Most basic features are supported. Several advanced features are not supported.
Some may be added in a future release.

View File

@ -41,5 +41,11 @@ JSON:
LGPLv2.1
<br>
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>
</html>

View File

@ -49,7 +49,7 @@
<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/vuze.js"></script>
<title>Vuze Remote</title>
<title>I2PSnark Remote</title>
</head>
<body id="transmission_body">
@ -62,7 +62,9 @@
<div id="toolbar-remove" title="Remove Selected 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-search" title="Search"></div>
-->
<div id="toolbar-inspector" title="Toggle Inspector"></div>
</div>
@ -92,7 +94,7 @@
</div>
<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 id="statusbar">

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB