Changeset b55bfd03


Ignore:
Timestamp:
Jan 13, 2011 4:32:26 PM (9 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
461e3b6
Parents:
65c61864 (diff), b4e0fe12 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

propagate from branch 'i2p.i2p.zzz.test4' (head 1b67209a056b1c17df560e857ee8b114a59254a3)

to branch 'i2p.i2p.zzz.dhtsnark' (head 463d86d9ccc9ed8c2faa79f2cf959effa8f92089)

Location:
apps/i2psnark
Files:
4 added
1 deleted
23 edited

Legend:

Unmodified
Added
Removed
  • apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java

    r65c61864 rb55bfd03  
    138138                }
    139139            } else {
     140                if (socket.getPeerDestination().equals(_util.getMyDestination())) {
     141                    _util.debug("Incoming connection from myself", Snark.ERROR);
     142                    try { socket.close(); } catch (IOException ioe) {}
     143                    continue;
     144                }
    140145                Thread t = new I2PAppThread(new Handler(socket), "I2PSnark incoming connection");
    141146                t.start();
     
    175180              InputStream in = _socket.getInputStream();
    176181              OutputStream out = _socket.getOutputStream();
    177 
    178               if (true) {
    179                   in = new BufferedInputStream(in);
    180                   //out = new BufferedOutputStream(out);
    181               }
     182              // this is for the readahead in PeerAcceptor.connection()
     183              in = new BufferedInputStream(in);
    182184              if (_log.shouldLog(Log.DEBUG))
    183185                  _log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
  • apps/i2psnark/java/src/org/klomp/snark/CoordinatorListener.java

    r65c61864 rb55bfd03  
    3232  void peerChange(PeerCoordinator coordinator, Peer peer);
    3333
     34  /**
     35   * Called when the PeerCoordinator got the MetaInfo via magnet.
     36   * @since 0.8.4
     37   */
     38  void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo);
     39
    3440  public boolean overUploadLimit(int uploaders);
    3541  public boolean overUpBWLimit();
  • apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java

    r65c61864 rb55bfd03  
    3535import net.i2p.util.Translate;
    3636
     37import org.klomp.snark.dht.DHT;
     38import org.klomp.snark.dht.KRPC;
     39
    3740/**
    3841 * I2P specific helpers for I2PSnark
     
    5962    private File _tmpDir;
    6063    private int _startupDelay;
     64    private boolean _shouldUseOT;
     65    private DHT _dht;
    6166
    6267    public static final int DEFAULT_STARTUP_DELAY = 3;
     
    6772    public static final int DEFAULT_MAX_UP_BW = 8;  //KBps
    6873    public static final int MAX_CONNECTIONS = 16; // per torrent
     74    private static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
     75    private static final boolean ENABLE_DHT = true;
     76
    6977    public I2PSnarkUtil(I2PAppContext ctx) {
    7078        _context = ctx;
     
    7987        _maxConnections = MAX_CONNECTIONS;
    8088        _startupDelay = DEFAULT_STARTUP_DELAY;
     89        _shouldUseOT = DEFAULT_USE_OPENTRACKERS;
    8190        // This is used for both announce replies and .torrent file downloads,
    8291        // so it must be available even if not connected to I2CP.
     
    125134    }
    126135   
     136    /**
     137     *  @param KBps
     138     */
    127139    public void setMaxUpBW(int limit) {
    128140        _maxUpBW = limit;
     141        _opts.put(PROP_MAX_BW, Integer.toString(limit * (1024 * 6 / 5)));   // add a little for overhead
    129142        _configured = true;
     143        if (_manager != null) {
     144            I2PSession sess = _manager.getSession();
     145            if (sess != null) {
     146                Properties newProps = new Properties();
     147                newProps.putAll(_opts);
     148                sess.updateOptions(newProps);
     149            }
     150        }
    130151    }
    131152   
     
    147168    public boolean getEepProxySet() { return _shouldProxy; }
    148169    public int getMaxUploaders() { return _maxUploaders; }
     170
     171    /**
     172     *  @return KBps
     173     */
    149174    public int getMaxUpBW() { return _maxUpBW; }
    150175    public int getMaxConnections() { return _maxConnections; }
     
    159184            if (_log.shouldLog(Log.DEBUG))
    160185                _log.debug("Connecting to I2P", new Exception("I did it"));
    161             Properties opts = new Properties();
     186            Properties opts = _context.getProperties();
    162187            if (_opts != null) {
    163188                for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
     
    188213            _manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
    189214        }
     215        // FIXME this only instantiates krpc once, left stuck with old manager
     216        if (ENABLE_DHT && _manager != null && _dht == null)
     217            _dht = new KRPC(_context, _manager.getSession());
    190218        return (_manager != null);
    191219    }
    192220   
     221    /**
     222     * @return null if disabled or not started
     223     * @since 0.8.4
     224     */
     225    public DHT getDHT() { return _dht; }
     226
    193227    public boolean connected() { return _manager != null; }
     228
    194229    /**
    195230     * Destroy the destination itself
     
    215250        if (addr == null)
    216251            throw new IOException("Null address");
     252        if (addr.equals(getMyDestination()))
     253            throw new IOException("Attempt to connect to myself");
    217254        Hash dest = addr.calculateHash();
    218255        if (_shitlist.contains(dest))
     
    288325   
    289326    String getOurIPString() {
     327        Destination dest = getMyDestination();
     328        if (dest != null)
     329            return dest.toBase64();
     330        return "unknown";
     331    }
     332
     333    /**
     334     *  @return dest or null
     335     *  @since 0.8.4
     336     */
     337    Destination getMyDestination() {
    290338        if (_manager == null)
    291             return "unknown";
     339            return null;
    292340        I2PSession sess = _manager.getSession();
    293         if (sess != null) {
    294             Destination dest = sess.getMyDestination();
    295             if (dest != null)
    296                 return dest.toBase64();
    297         }
    298         return "unknown";
     341        if (sess != null)
     342            return sess.getMyDestination();
     343        return null;
    299344    }
    300345
     
    401446    /** comma delimited list open trackers to use as backups */
    402447    /** sorted map of name to announceURL=baseURL */
    403     public List getOpenTrackers() {
     448    public List<String> getOpenTrackers() {
    404449        if (!shouldUseOpenTrackers())
    405450            return null;
    406         List rv = new ArrayList(1);
     451        List<String> rv = new ArrayList(1);
    407452        String trackers = getOpenTrackerString();
    408453        StringTokenizer tok = new StringTokenizer(trackers, ", ");
     
    415460    }
    416461   
     462    public void setUseOpenTrackers(boolean yes) {
     463        _shouldUseOT = yes;
     464    }
     465
    417466    public boolean shouldUseOpenTrackers() {
    418         String rv = (String) _opts.get(PROP_USE_OPENTRACKERS);
    419         if (rv == null)
    420             return DEFAULT_USE_OPENTRACKERS;
    421         return Boolean.valueOf(rv).booleanValue();
     467        return _shouldUseOT;
     468    }
     469
     470    /**
     471     *  Like DataHelper.toHexString but ensures no loss of leading zero bytes
     472     *  @since 0.8.4
     473     */
     474    public static String toHex(byte[] b) {
     475        StringBuilder buf = new StringBuilder(40);
     476        for (int i = 0; i < b.length; i++) {
     477            int bi = b[i] & 0xff;
     478            if (bi < 16)
     479                buf.append('0');
     480            buf.append(Integer.toHexString(bi));
     481        }
     482        return buf.toString();
    422483    }
    423484
  • apps/i2psnark/java/src/org/klomp/snark/Message.java

    r65c61864 rb55bfd03  
    5454  // Used for HAVE, REQUEST, PIECE and CANCEL messages.
    5555  // low byte used for EXTENSION message
     56  // low two bytes used for PORT message
    5657  int piece;
    5758
     
    6869  DataLoader dataLoader;
    6970
    70   SimpleTimer.TimedEvent expireEvent;
     71  // now unused
     72  //SimpleTimer.TimedEvent expireEvent;
    7173 
    7274  /** Utility method for sending a message through a DataStream. */
     
    104106      datalen += 4;
    105107
    106     // length is 1 byte
     108    // msg type is 1 byte
    107109    if (type == EXTENSION)
    108110      datalen += 1;
     111
     112    if (type == PORT)
     113      datalen += 2;
    109114
    110115    // add length of data for piece or bitfield array.
     
    130135    if (type == EXTENSION)
    131136        dos.writeByte((byte) piece & 0xff);
     137
     138    if (type == PORT)
     139        dos.writeShort(piece & 0xffff);
    132140
    133141    // Send actual data
     
    161169      case CANCEL:
    162170        return "CANCEL(" + piece + "," + begin + "," + length + ")";
     171      case PORT:
     172        return "PORT(" + piece + ")";
    163173      case EXTENSION:
    164174        return "EXTENSION(" + piece + ',' + data.length + ')';
  • apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java

    r65c61864 rb55bfd03  
    2626import java.security.NoSuchAlgorithmException;
    2727import java.util.ArrayList;
     28import java.util.Collections;
    2829import java.util.HashMap;
    2930import java.util.Iterator;
     
    5455  private final String name;
    5556  private final String name_utf8;
    56   private final List files;
    57   private final List files_utf8;
    58   private final List lengths;
     57  private final List<List<String>> files;
     58  private final List<List<String>> files_utf8;
     59  private final List<Long> lengths;
    5960  private final int piece_length;
    6061  private final byte[] piece_hashes;
    6162  private final long length;
    62   private final Map infoMap;
    63 
    64   private byte[] torrentdata;
    65 
    66   MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
     63  private Map<String, BEValue> infoMap;
     64
     65  /**
     66   *  Called by Storage when creating a new torrent from local data
     67   *
     68   *  @param announce may be null
     69   *  @param files null for single-file torrent
     70   *  @param lengths null for single-file torrent
     71   */
     72  MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
    6773           int piece_length, byte[] piece_hashes, long length)
    6874  {
     
    7076    this.name = name;
    7177    this.name_utf8 = name_utf8;
    72     this.files = files;
     78    this.files = files == null ? null : Collections.unmodifiableList(files);
    7379    this.files_utf8 = null;
    74     this.lengths = lengths;
     80    this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths);
    7581    this.piece_length = piece_length;
    7682    this.piece_hashes = piece_hashes;
     
    7884
    7985    this.info_hash = calculateInfoHash();
    80     infoMap = null;
     86    //infoMap = null;
    8187  }
    8288
     
    8591   * InputStream must start with a correctly bencoded dictonary
    8692   * describing the torrent.
     93   * Caller must close the stream.
    8794   */
    8895  public MetaInfo(InputStream in) throws IOException
     
    105112   * the original bencoded info dictonary (this is a hack, we could
    106113   * reconstruct the bencoded stream and recalculate the hash). Will
    107    * throw a InvalidBEncodingException if the given map does not
    108    * contain a valid announce string or info dictonary.
     114   * NOT throw a InvalidBEncodingException if the given map does not
     115   * contain a valid announce string.
     116   * WILL throw a InvalidBEncodingException if the given map does not
     117   * contain a valid info dictionary.
    109118   */
    110119  public MetaInfo(Map m) throws InvalidBEncodingException
     
    113122        _log.debug("Creating a metaInfo: " + m, new Exception("source"));
    114123    BEValue val = (BEValue)m.get("announce");
    115     if (val == null)
    116         throw new InvalidBEncodingException("Missing announce string");
    117     this.announce = val.getString();
     124    // Disabled check, we can get info from a magnet now
     125    if (val == null) {
     126        //throw new InvalidBEncodingException("Missing announce string");
     127        this.announce = null;
     128    } else {
     129        this.announce = val.getString();
     130    }
    118131
    119132    val = (BEValue)m.get("info");
     
    121134        throw new InvalidBEncodingException("Missing info map");
    122135    Map info = val.getMap();
    123     infoMap = info;
     136    infoMap = Collections.unmodifiableMap(info);
    124137
    125138    val = (BEValue)info.get("name");
     
    161174            ("Missing length number and/or files list");
    162175
    163         List list = val.getList();
     176        List<BEValue> list = val.getList();
    164177        int size = list.size();
    165178        if (size == 0)
    166179          throw new InvalidBEncodingException("zero size files list");
    167180
    168         files = new ArrayList(size);
    169         files_utf8 = new ArrayList(size);
    170         lengths = new ArrayList(size);
     181        List<List<String>> m_files = new ArrayList(size);
     182        List<List<String>> m_files_utf8 = new ArrayList(size);
     183        List<Long> m_lengths = new ArrayList(size);
    171184        long l = 0;
    172185        for (int i = 0; i < list.size(); i++)
    173186          {
    174             Map desc = ((BEValue)list.get(i)).getMap();
    175             val = (BEValue)desc.get("length");
     187            Map<String, BEValue> desc = list.get(i).getMap();
     188            val = desc.get("length");
    176189            if (val == null)
    177190              throw new InvalidBEncodingException("Missing length number");
    178191            long len = val.getLong();
    179             lengths.add(new Long(len));
     192            m_lengths.add(Long.valueOf(len));
    180193            l += len;
    181194
     
    183196            if (val == null)
    184197              throw new InvalidBEncodingException("Missing path list");
    185             List path_list = val.getList();
     198            List<BEValue> path_list = val.getList();
    186199            int path_length = path_list.size();
    187200            if (path_length == 0)
    188201              throw new InvalidBEncodingException("zero size file path list");
    189202
    190             List file = new ArrayList(path_length);
    191             Iterator it = path_list.iterator();
     203            List<String> file = new ArrayList(path_length);
     204            Iterator<BEValue> it = path_list.iterator();
    192205            while (it.hasNext())
    193               file.add(((BEValue)it.next()).getString());
    194 
    195             files.add(file);
     206              file.add(it.next().getString());
     207
     208            m_files.add(Collections.unmodifiableList(file));
    196209           
    197210            val = (BEValue)desc.get("path.utf-8");
     
    203216                    it = path_list.iterator();
    204217                    while (it.hasNext())
    205                         file.add(((BEValue)it.next()).getString());
    206                     files_utf8.add(file);
     218                        file.add(it.next().getString());
     219                    m_files_utf8.add(Collections.unmodifiableList(file));
    207220                }
    208221            }
    209222          }
     223        files = Collections.unmodifiableList(m_files);
     224        files_utf8 = Collections.unmodifiableList(m_files_utf8);
     225        lengths = Collections.unmodifiableList(m_lengths);
    210226        length = l;
    211227      }
     
    216232  /**
    217233   * Returns the string representing the URL of the tracker for this torrent.
     234   * @return may be null!
    218235   */
    219236  public String getAnnounce()
     
    254271   * getLengths().
    255272   */
    256   public List getFiles()
    257   {
    258     // XXX - Immutable?
     273  public List<List<String>> getFiles()
     274  {
    259275    return files;
    260276  }
     
    265281   * the list returned by getFiles().
    266282   */
    267   public List getLengths()
    268   {
    269     // XXX - Immutable?
     283  public List<Long> getLengths()
     284  {
    270285    return lengths;
    271286  }
     
    389404  }
    390405
    391   public byte[] getTorrentData()
    392   {
    393     if (torrentdata == null)
    394       {
     406  /**
     407   *  Called by servlet to save a new torrent file generated from local data
     408   */
     409  public synchronized byte[] getTorrentData()
     410  {
    395411        Map m = new HashMap();
    396         m.put("announce", announce);
     412        if (announce != null)
     413            m.put("announce", announce);
    397414        Map info = createInfoMap();
    398415        m.put("info", info);
    399         torrentdata = BEncoder.bencode(m);
    400       }
    401     return torrentdata;
    402   }
    403 
    404   private Map createInfoMap()
    405   {
     416        // don't save this locally, we should only do this once
     417        return BEncoder.bencode(m);
     418  }
     419
     420  /** @since 0.8.4 */
     421  public synchronized byte[] getInfoBytes() {
     422    if (infoMap == null)
     423        createInfoMap();
     424    return BEncoder.bencode(infoMap);
     425  }
     426
     427  /** @return an unmodifiable view of the Map */
     428  private Map<String, BEValue> createInfoMap()
     429  {
     430    // if we loaded this metainfo from a file, we have the map
     431    if (infoMap != null)
     432        return Collections.unmodifiableMap(infoMap);
     433    // otherwise we must create it
    406434    Map info = new HashMap();
    407     if (infoMap != null) {
    408         info.putAll(infoMap);
    409         return info;
    410     }
    411435    info.put("name", name);
    412436    if (name_utf8 != null)
     
    415439    info.put("pieces", piece_hashes);
    416440    if (files == null)
    417       info.put("length", new Long(length));
     441      info.put("length", Long.valueOf(length));
    418442    else
    419443      {
     
    430454        info.put("files", l);
    431455      }
    432     return info;
     456    infoMap = info;
     457    return Collections.unmodifiableMap(infoMap);
    433458  }
    434459
  • apps/i2psnark/java/src/org/klomp/snark/Peer.java

    r65c61864 rb55bfd03  
    2121package org.klomp.snark;
    2222
    23 import java.io.BufferedInputStream;
    2423import java.io.DataInputStream;
    2524import java.io.DataOutputStream;
     
    2928import java.util.Arrays;
    3029import java.util.List;
     30import java.util.Map;
    3131
    3232import net.i2p.client.streaming.I2PSocket;
     33import net.i2p.data.DataHelper;
     34import net.i2p.data.Destination;
    3335import net.i2p.util.Log;
     36
     37import org.klomp.snark.bencode.BEValue;
    3438
    3539public class Peer implements Comparable
     
    4044
    4145  private final byte[] my_id;
    42   final MetaInfo metainfo;
     46  private final byte[] infohash;
     47  /** will start out null in magnet mode */
     48  private MetaInfo metainfo;
     49  private Map<String, BEValue> handshakeMap;
    4350
    4451  // The data in/output streams set during the handshake and used by
     
    4754  private DataOutputStream dout;
    4855
     56  /** running counters */
     57  private long downloaded;
     58  private long uploaded;
     59
    4960  // Keeps state for in/out connections.  Non-null when the handshake
    5061  // was successful, the connection setup and runs
    5162  PeerState state;
     63
     64  /** shared across all peers on this torrent */
     65  MagnetState magnetState;
    5266
    5367  private I2PSocket sock;
     
    6579  static final long OPTION_FAST      = 0x0000000000000004l;
    6680  static final long OPTION_DHT       = 0x0000000000000001l;
     81  static final long OPTION_AZMP      = 0x1000000000000000l;
    6782  private long options;
    6883
     
    7186   * Creates a disconnected peer given a PeerID, your own id and the
    7287   * relevant MetaInfo.
    73    */
    74   public Peer(PeerID peerID, byte[] my_id, MetaInfo metainfo)
    75     throws IOException
     88   * @param metainfo null if in magnet mode
     89   */
     90  public Peer(PeerID peerID, byte[] my_id, byte[] infohash, MetaInfo metainfo)
    7691  {
    7792    this.peerID = peerID;
    7893    this.my_id = my_id;
     94    this.infohash = infohash;
    7995    this.metainfo = metainfo;
    8096    _id = ++__id;
     
    90106   * the connect() method.
    91107   *
     108   * @param metainfo null if in magnet mode
    92109   * @exception IOException when an error occurred during the handshake.
    93110   */
    94   public Peer(final I2PSocket sock, InputStream in, OutputStream out, byte[] my_id, MetaInfo metainfo)
     111  public Peer(final I2PSocket sock, InputStream in, OutputStream out, byte[] my_id, byte[] infohash, MetaInfo metainfo)
    95112    throws IOException
    96113  {
    97114    this.my_id = my_id;
     115    this.infohash = infohash;
    98116    this.metainfo = metainfo;
    99117    this.sock = sock;
     
    103121    _id = ++__id;
    104122    if (_log.shouldLog(Log.DEBUG))
    105         _log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating " + _id));
     123        _log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id));
    106124  }
    107125
     
    193211   * message.
    194212   */
    195   public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield)
     213  public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield, MagnetState mState)
    196214  {
    197215    if (state != null)
     
    213231            }
    214232            InputStream in = sock.getInputStream();
    215             OutputStream out = sock.getOutputStream(); //new BufferedOutputStream(sock.getOutputStream());
    216             if (true) {
    217                 // buffered output streams are internally synchronized, so we can't get through to the underlying
    218                 // I2PSocket's MessageOutputStream to close() it if we are blocking on a write(...).  Oh, and the
    219                 // buffer is unnecessary anyway, as unbuffered access lets the streaming lib do the 'right thing'.
    220                 //out = new BufferedOutputStream(out);
    221                 in = new BufferedInputStream(sock.getInputStream());
    222             }
    223             //BufferedInputStream bis
    224             //  = new BufferedInputStream(sock.getInputStream());
    225             //BufferedOutputStream bos
    226             //  = new BufferedOutputStream(sock.getOutputStream());
    227             byte [] id = handshake(in, out); //handshake(bis, bos);
     233            OutputStream out = sock.getOutputStream();
     234            byte [] id = handshake(in, out);
    228235            byte [] expected_id = peerID.getID();
    229236            if (expected_id == null) {
     
    244251          }
    245252       
     253        // bad idea?
     254        if (metainfo == null && (options & OPTION_EXTENSION) == 0) {
     255            if (_log.shouldLog(Log.INFO))
     256                _log.info("Peer does not support extensions and we need metainfo, dropping");
     257            throw new IOException("Peer does not support extensions and we need metainfo, dropping");
     258        }
     259
    246260        PeerConnectionIn in = new PeerConnectionIn(this, din);
    247261        PeerConnectionOut out = new PeerConnectionOut(this, dout);
     
    250264        if ((options & OPTION_EXTENSION) != 0) {
    251265            if (_log.shouldLog(Log.DEBUG))
    252                 _log.debug("Peer supports extensions, sending test message");
    253             out.sendExtension(0, ExtensionHandshake.getPayload());
     266                _log.debug("Peer supports extensions, sending reply message");
     267            int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1;
     268            out.sendExtension(0, ExtensionHandler.getHandshake(metasize));
     269        }
     270
     271        if ((options & OPTION_DHT) != 0 && util.getDHT() != null) {
     272            if (_log.shouldLog(Log.DEBUG))
     273                _log.debug("Peer supports DHT, sending PORT message");
     274            int port = util.getDHT().getPort();
     275            out.sendPort(port);
    254276        }
    255277
     
    260282        // We are up and running!
    261283        state = s;
     284        magnetState = mState;
    262285        listener.connected(this);
    263286 
     
    294317   * reported by the other side.
    295318   */
    296   private byte[] handshake(InputStream in, OutputStream out) //BufferedInputStream bis, BufferedOutputStream bos)
     319  private byte[] handshake(InputStream in, OutputStream out)
    297320    throws IOException
    298321  {
     
    304327    dout.write("BitTorrent protocol".getBytes("UTF-8"));
    305328    // Handshake write - options
    306     dout.writeLong(OPTION_EXTENSION);
     329    // FIXME not if DHT disabled
     330    dout.writeLong(OPTION_EXTENSION | OPTION_DHT);
    307331    // Handshake write - metainfo hash
    308     byte[] shared_hash = metainfo.getInfoHash();
    309     dout.write(shared_hash);
     332    dout.write(infohash);
    310333    // Handshake write - peer id
    311334    dout.write(my_id);
     
    335358    bs = new byte[20];
    336359    din.readFully(bs);
    337     if (!Arrays.equals(shared_hash, bs))
     360    if (!Arrays.equals(infohash, bs))
    338361      throw new IOException("Unexpected MetaInfo hash");
    339362
     
    343366        _log.debug("Read the remote side's hash and peerID fully from " + toString());
    344367
     368    if (DataHelper.eq(my_id, bs))
     369        throw new IOException("Connected to myself");
     370
    345371    if (options != 0) {
    346         // send them something
     372        // send them something in runConnection() above
    347373        if (_log.shouldLog(Log.DEBUG))
    348374            _log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
     
    350376
    351377    return bs;
     378  }
     379
     380  /** @since 0.8.4 */
     381  public long getOptions() {
     382      return options;
     383  }
     384
     385  /** @since 0.8.4 */
     386  public Destination getDestination() {
     387      if (sock == null)
     388          return null;
     389      return sock.getPeerDestination();
     390  }
     391
     392  /**
     393   *  Shared state across all peers, callers must sync on returned object
     394   *  @return non-null
     395   *  @since 0.8.4
     396   */
     397  public MagnetState getMagnetState() {
     398      return magnetState;
     399  }
     400
     401  /** @return could be null @since 0.8.4 */
     402  public Map<String, BEValue> getHandshakeMap() {
     403      return handshakeMap;
     404  }
     405
     406  /** @since 0.8.4 */
     407  public void setHandshakeMap(Map<String, BEValue> map) {
     408      handshakeMap = map;
     409  }
     410
     411  /** @since 0.8.4 */
     412  public void sendExtension(int type, byte[] payload) {
     413    PeerState s = state;
     414    if (s != null)
     415        s.out.sendExtension(type, payload);
     416  }
     417
     418  /**
     419   *  Switch from magnet mode to normal mode
     420   *  @since 0.8.4
     421   */
     422  public void setMetaInfo(MetaInfo meta) {
     423    metainfo = meta;
     424    PeerState s = state;
     425    if (s != null)
     426        s.setMetaInfo(meta);
    352427  }
    353428
     
    515590
    516591  /**
     592   * Increment the counter.
     593   * @since 0.8.4
     594   */
     595  public void downloaded(int size) {
     596      downloaded += size;
     597  }
     598
     599  /**
     600   * Increment the counter.
     601   * @since 0.8.4
     602   */
     603  public void uploaded(int size) {
     604      uploaded += size;
     605  }
     606
     607  /**
    517608   * Returns the number of bytes that have been downloaded.
    518609   * Can be reset to zero with <code>resetCounters()</code>/
     
    520611  public long getDownloaded()
    521612  {
    522     PeerState s = state;
    523     return (s != null) ? s.downloaded : 0;
     613      return downloaded;
    524614  }
    525615
     
    530620  public long getUploaded()
    531621  {
    532     PeerState s = state;
    533     return (s != null) ? s.uploaded : 0;
     622      return uploaded;
    534623  }
    535624
     
    539628  public void resetCounters()
    540629  {
    541     PeerState s = state;
    542     if (s != null)
    543       {
    544         s.downloaded = 0;
    545         s.uploaded = 0;
    546       }
     630      downloaded = 0;
     631      uploaded = 0;
    547632  }
    548633 
  • apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java

    r65c61864 rb55bfd03  
    8989    if (coordinator != null) {
    9090        // single torrent capability
    91         MetaInfo meta = coordinator.getMetaInfo();
    92         if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
     91        if (DataHelper.eq(coordinator.getInfoHash(), peerInfoHash)) {
    9392            if (coordinator.needPeers())
    9493              {
    9594                Peer peer = new Peer(socket, in, out, coordinator.getID(),
    96                                      coordinator.getMetaInfo());
     95                                     coordinator.getInfoHash(), coordinator.getMetaInfo());
    9796                coordinator.addPeer(peer);
    9897              }
     
    102101          // its for another infohash, but we are only single torrent capable.  b0rk.
    103102            throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
    104                                   + ") while we only support (" + Base64.encode(meta.getInfoHash()) + ")");
     103                                  + ") while we only support (" + Base64.encode(coordinator.getInfoHash()) + ")");
    105104        }
    106105    } else {
     
    108107        for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
    109108            PeerCoordinator cur = (PeerCoordinator)iter.next();
    110             MetaInfo meta = cur.getMetaInfo();
    111            
    112             if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
     109
     110            if (DataHelper.eq(cur.getInfoHash(), peerInfoHash)) {
    113111                if (cur.needPeers())
    114112                  {
    115113                    Peer peer = new Peer(socket, in, out, cur.getID(),
    116                                          cur.getMetaInfo());
     114                                         cur.getInfoHash(), cur.getMetaInfo());
    117115                    cur.addPeer(peer);
    118116                    return;
     
    121119                  {
    122120                    if (_log.shouldLog(Log.DEBUG))
    123                       _log.debug("Rejecting new peer for " + cur.snark.torrent);
     121                      _log.debug("Rejecting new peer for " + cur.getName());
    124122                    socket.close();
    125123                    return;
  • apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java

    r65c61864 rb55bfd03  
    3838
    3939  private final PeerCoordinator coordinator;
    40   public I2PSnarkUtil _util;
     40  private final I2PSnarkUtil _util;
     41  private int _runCount;
    4142
    4243  PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
     
    5051  public void run()
    5152  {
     53        _runCount++;
    5254        List<Peer> peerList = coordinator.peerList();
    5355        if (peerList.isEmpty() || coordinator.halted()) {
    54           coordinator.peerCount = 0;
    55           coordinator.interestedAndChoking = 0;
    5656          coordinator.setRateHistory(0, 0);
    57           coordinator.uploaders = 0;
    5857          if (coordinator.halted())
    5958            cancel();
     
    208207            peer.retransmitRequests();
    209208            peer.keepAlive();
     209            // announce them to local tracker (TrackerClient does this too)
     210            if (_util.getDHT() != null && (_runCount % 5) == 0) {
     211                _util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash());
     212            }
     213            // send PEX
    210214          }
    211215
     
    248252
    249253        // close out unused files, but we don't need to do it every time
    250         if (random.nextInt(4) == 0)
    251             coordinator.getStorage().cleanRAFs();
    252 
     254        Storage storage = coordinator.getStorage();
     255        if (storage != null && (_runCount % 4) == 0) {
     256                storage.cleanRAFs();
     257        }
     258
     259        // announce ourselves to local tracker (TrackerClient does this too)
     260        if (_util.getDHT() != null && (_runCount % 16) == 0) {
     261            _util.getDHT().announce(coordinator.getInfoHash());
     262        }
    253263  }
    254264}
  • apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java

    r65c61864 rb55bfd03  
    3333  private final DataInputStream din;
    3434
     35  // The max length of a complete message in bytes.
     36  // The biggest is the piece message, for which the length is the
     37  // request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
     38  // in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
     39  private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9,
     40                                                   MagnetState.CHUNK_SIZE + 100);  // 100 for the ext msg dictionary
     41
    3542  private Thread thread;
    3643  private volatile boolean quit;
     
    7885       
    7986            // Wait till we hear something...
    80             // The length of a complete message in bytes.
    81             // The biggest is the piece message, for which the length is the
    82             // request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
    83             // in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
    8487            int i = din.readInt();
    8588            lastRcvd = System.currentTimeMillis();
    86             if (i < 0 || i > PeerState.PARTSIZE + 9)
     89            if (i < 0 || i > MAX_MSG_SIZE)
    8790              throw new IOException("Unexpected length prefix: " + i);
    8891
     
    9194                ps.keepAliveMessage();
    9295                if (_log.shouldLog(Log.DEBUG))
    93                     _log.debug("Received keepalive from " + peer + " on " + peer.metainfo.getName());
     96                    _log.debug("Received keepalive from " + peer);
    9497                continue;
    9598              }
     
    103106                ps.chokeMessage(true);
    104107                if (_log.shouldLog(Log.DEBUG))
    105                     _log.debug("Received choke from " + peer + " on " + peer.metainfo.getName());
     108                    _log.debug("Received choke from " + peer);
    106109                break;
    107110              case 1:
    108111                ps.chokeMessage(false);
    109112                if (_log.shouldLog(Log.DEBUG))
    110                     _log.debug("Received unchoke from " + peer + " on " + peer.metainfo.getName());
     113                    _log.debug("Received unchoke from " + peer);
    111114                break;
    112115              case 2:
    113116                ps.interestedMessage(true);
    114117                if (_log.shouldLog(Log.DEBUG))
    115                     _log.debug("Received interested from " + peer + " on " + peer.metainfo.getName());
     118                    _log.debug("Received interested from " + peer);
    116119                break;
    117120              case 3:
    118121                ps.interestedMessage(false);
    119122                if (_log.shouldLog(Log.DEBUG))
    120                     _log.debug("Received not interested from " + peer + " on " + peer.metainfo.getName());
     123                    _log.debug("Received not interested from " + peer);
    121124                break;
    122125              case 4:
     
    124127                ps.haveMessage(piece);
    125128                if (_log.shouldLog(Log.DEBUG))
    126                     _log.debug("Received havePiece(" + piece + ") from " + peer + " on " + peer.metainfo.getName());
     129                    _log.debug("Received havePiece(" + piece + ") from " + peer);
    127130                break;
    128131              case 5:
     
    131134                ps.bitfieldMessage(bitmap);
    132135                if (_log.shouldLog(Log.DEBUG))
    133                     _log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
     136                    _log.debug("Received bitmap from " + peer + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
    134137                break;
    135138              case 6:
     
    139142                ps.requestMessage(piece, begin, len);
    140143                if (_log.shouldLog(Log.DEBUG))
    141                     _log.debug("Received request(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
     144                    _log.debug("Received request(" + piece + "," + begin + ") from " + peer);
    142145                break;
    143146              case 7:
     
    153156                    ps.pieceMessage(req);
    154157                    if (_log.shouldLog(Log.DEBUG))
    155                         _log.debug("Received data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
     158                        _log.debug("Received data(" + piece + "," + begin + ") from " + peer);
    156159                  }
    157160                else
     
    161164                    din.readFully(piece_bytes);
    162165                    if (_log.shouldLog(Log.DEBUG))
    163                         _log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
     166                        _log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
    164167                  }
    165168                break;
     
    170173                ps.cancelMessage(piece, begin, len);
    171174                if (_log.shouldLog(Log.DEBUG))
    172                     _log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
     175                    _log.debug("Received cancel(" + piece + "," + begin + ") from " + peer);
     176                break;
     177              case 9:  // PORT message
     178                int port = din.readUnsignedShort();
     179                ps.portMessage(port);
     180                if (_log.shouldLog(Log.DEBUG))
     181                    _log.debug("Received port message from " + peer);
    173182                break;
    174183              case 20:  // Extension message
     
    176185                byte[] payload = new byte[i-2];
    177186                din.readFully(payload);
     187                if (_log.shouldLog(Log.DEBUG))
     188                    _log.debug("Received extension message from " + peer);
    178189                ps.extensionMessage(id, payload);
    179                 if (_log.shouldLog(Log.DEBUG))
    180                     _log.debug("Received extension message from " + peer + " on " + peer.metainfo.getName());
    181190                break;
    182191              default:
     
    185194                ps.unknownMessage(b, bs);
    186195                if (_log.shouldLog(Log.DEBUG))
    187                     _log.debug("Received unknown message from " + peer + " on " + peer.metainfo.getName());
     196                    _log.debug("Received unknown message from " + peer);
    188197              }
    189198          }
  • apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java

    r65c61864 rb55bfd03  
    3030import net.i2p.util.I2PAppThread;
    3131import net.i2p.util.Log;
    32 import net.i2p.util.SimpleScheduler;
    33 import net.i2p.util.SimpleTimer;
     32//import net.i2p.util.SimpleScheduler;
     33//import net.i2p.util.SimpleTimer;
    3434
    3535class PeerConnectionOut implements Runnable
     
    125125                            if (state.choking) {
    126126                              it.remove();
    127                               SimpleTimer.getInstance().removeEvent(nm.expireEvent);
     127                              //SimpleTimer.getInstance().removeEvent(nm.expireEvent);
    128128                            }
    129129                            nm = null;
     
    132132                          {
    133133                            it.remove();
    134                             SimpleTimer.getInstance().removeEvent(nm.expireEvent);
     134                            //SimpleTimer.getInstance().removeEvent(nm.expireEvent);
    135135                            nm = null;
    136136                          }
     
    139139                          {
    140140                            m = nm;
    141                             SimpleTimer.getInstance().removeEvent(nm.expireEvent);
     141                            //SimpleTimer.getInstance().removeEvent(nm.expireEvent);
    142142                            it.remove();
    143143                          }
     
    145145                    if (m == null && !sendQueue.isEmpty()) {
    146146                      m = (Message)sendQueue.remove(0);
    147                       SimpleTimer.getInstance().removeEvent(m.expireEvent);
     147                      //SimpleTimer.getInstance().removeEvent(m.expireEvent);
    148148                    }
    149149                  }
     
    152152              {
    153153                if (_log.shouldLog(Log.DEBUG))
    154                     _log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
     154                    _log.debug("Send " + peer + ": " + m);
    155155
    156156                // This can block for quite a while.
     
    242242  /** remove messages not sent in 3m */
    243243  private static final int SEND_TIMEOUT = 3*60*1000;
     244
     245/*****
    244246  private class RemoveTooSlow implements SimpleTimer.TimedEvent {
    245247      private Message _m;
     
    259261      }
    260262  }
     263*****/
    261264
    262265  /**
     
    475478    m.len = length;
    476479    // since we have the data already loaded, queue a timeout to remove it
    477     SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
     480    // no longer prefetched
     481    //SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
    478482    addMessage(m);
    479483  }
     
    548552    addMessage(m);
    549553  }
     554
     555  /** @since 0.8.4 */
     556  void sendPort(int port) {
     557    Message m = new Message();
     558    m.type = Message.PORT;
     559    m.piece = port;
     560    addMessage(m);
     561  }
    550562}
  • apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java

    r65c61864 rb55bfd03  
    3737import net.i2p.util.SimpleTimer2;
    3838
     39import org.klomp.snark.dht.DHT;
     40
    3941/**
    4042 * Coordinates what peer does what.
     
    4345{
    4446  private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
    45   final MetaInfo metainfo;
    46   final Storage storage;
    47   final Snark snark;
     47
     48  /**
     49   * External use by PeerMonitorTask only.
     50   * Will be null when in magnet mode.
     51   */
     52  MetaInfo metainfo;
     53
     54  /**
     55   * External use by PeerMonitorTask only.
     56   * Will be null when in magnet mode.
     57   */
     58  Storage storage;
     59  private final Snark snark;
    4860
    4961  // package local for access by CheckDownLoadersTask
     
    5163  final static int MAX_UPLOADERS = 6;
    5264
    53   // Approximation of the number of current uploaders.
    54   // Resynced by PeerChecker once in a while.
    55   int uploaders = 0;
    56   int interestedAndChoking = 0;
     65  /**
     66   * Approximation of the number of current uploaders.
     67   * Resynced by PeerChecker once in a while.
     68   * External use by PeerCheckerTask only.
     69   */
     70  int uploaders;
     71
     72  /**
     73   * External use by PeerCheckerTask only.
     74   */
     75  int interestedAndChoking;
    5776
    5877  // final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
     
    6281  private long downloaded;
    6382  final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
    64   private long uploaded_old[] = {-1,-1,-1};
    65   private long downloaded_old[] = {-1,-1,-1};
    66 
    67   // synchronize on this when changing peers or downloaders
    68   // This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking
     83  private final long uploaded_old[] = {-1,-1,-1};
     84  private final long downloaded_old[] = {-1,-1,-1};
     85
     86  /**
     87   * synchronize on this when changing peers or downloaders.
     88   * This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking.
     89   * External use by PeerMonitorTask only.
     90   */
    6991  final Queue<Peer> peers;
     92
    7093  /** estimate of the peers, without requiring any synchronization */
    71   volatile int peerCount;
     94  private volatile int peerCount;
    7295
    7396  /** Timer to handle all periodical tasks. */
     
    7598
    7699  private final byte[] id;
     100  private final byte[] infohash;
    77101
    78102  /** The wanted pieces. We could use a TreeSet but we'd have to clear and re-add everything
     
    86110  private boolean halted = false;
    87111
     112  private final MagnetState magnetState;
    88113  private final CoordinatorListener listener;
    89   public I2PSnarkUtil _util;
     114  private final I2PSnarkUtil _util;
    90115  private static final Random _random = I2PAppContext.getGlobalContext().random();
    91116 
    92   public String trackerProblems = null;
    93   public int trackerSeenPeers = 0;
    94 
    95   public PeerCoordinator(I2PSnarkUtil util, byte[] id, MetaInfo metainfo, Storage storage,
     117  /**
     118   *  @param metainfo null if in magnet mode
     119   *  @param storage null if in magnet mode
     120   */
     121  public PeerCoordinator(I2PSnarkUtil util, byte[] id, byte[] infohash, MetaInfo metainfo, Storage storage,
    96122                         CoordinatorListener listener, Snark torrent)
    97123  {
    98124    _util = util;
    99125    this.id = id;
     126    this.infohash = infohash;
    100127    this.metainfo = metainfo;
    101128    this.storage = storage;
     
    107134    partialPieces = new ArrayList(getMaxConnections() + 1);
    108135    peers = new LinkedBlockingQueue();
     136    magnetState = new MagnetState(infohash, metainfo);
    109137
    110138    // Install a timer to check the uploaders.
     
    134162  public void setWantedPieces()
    135163  {
     164    if (metainfo == null || storage == null)
     165        return;
    136166    // Make a list of pieces
    137167      synchronized(wantedPieces) {
     
    154184
    155185  public Storage getStorage() { return storage; }
    156   public CoordinatorListener getListener() { return listener; }
    157186
    158187  // for web page detailed stats
     
    167196  }
    168197
     198  public String getName()
     199  {
     200    return snark.getName();
     201  }
     202
    169203  public boolean completed()
    170204  {
     205    // FIXME return metainfo complete status
     206    if (storage == null)
     207        return false;
    171208    return storage.complete();
    172209  }
     
    185222  /**
    186223   * Returns how many bytes are still needed to get the complete file.
     224   * @return -1 if in magnet mode
    187225   */
    188226  public long getLeft()
    189227  {
     228    if (metainfo == null | storage == null)
     229        return -1;
    190230    // XXX - Only an approximation.
    191231    return ((long) storage.needed()) * metainfo.getPieceLength(0);
     
    272312  }
    273313
     314  /** @since 0.8.4 */
     315  public byte[] getInfoHash()
     316  {
     317    return infohash;
     318  }
     319
    274320  public boolean needPeers()
    275321  {
     
    282328   */
    283329  private int getMaxConnections() {
     330    if (metainfo == null)
     331        return 6;
    284332    int size = metainfo.getPieceLength(0);
    285333    int max = _util.getMaxConnections();
     
    356404        else
    357405          {
    358             if (_log.shouldLog(Log.INFO))
    359               _log.info("New connection to peer: " + peer + " for " + metainfo.getName());
     406            if (_log.shouldLog(Log.INFO)) {
     407                // just for logging
     408                String name;
     409                if (metainfo == null)
     410                    name = "Magnet";
     411                else
     412                    name = metainfo.getName();
     413               _log.info("New connection to peer: " + peer + " for " + name);
     414            }
    360415
    361416            // Add it to the beginning of the list.
     
    416471    if (need_more)
    417472      {
    418         if (_log.shouldLog(Log.DEBUG))
    419             _log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run"));
    420 
     473        if (_log.shouldLog(Log.DEBUG)) {
     474            // just for logging
     475            String name;
     476            if (metainfo == null)
     477                name = "Magnet";
     478            else
     479                name = metainfo.getName();
     480            _log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + name, new Exception("add/run"));
     481        }
    421482        // Run the peer with us as listener and the current bitfield.
    422483        final PeerListener listener = this;
    423         final BitField bitfield = storage.getBitField();
     484        final BitField bitfield;
     485        if (storage != null)
     486            bitfield = storage.getBitField();
     487        else
     488            bitfield = null;
    424489        Runnable r = new Runnable()
    425490          {
    426491            public void run()
    427492            {
    428               peer.runConnection(_util, listener, bitfield);
     493              peer.runConnection(_util, listener, bitfield, magnetState);
    429494            }
    430495          };
     
    485550          }
    486551        interestedAndChoking = count;
    487   }
    488 
    489   public byte[] getBitMap()
    490   {
    491     return storage.getBitField().getFieldBytes();
    492552  }
    493553
     
    648708   */
    649709  public void updatePiecePriorities() {
     710      if (storage == null)
     711          return;
    650712      int[] pri = storage.getPiecePriorities();
    651713      if (pri == null) {
     
    714776    if (halted)
    715777      return null;
     778    if (metainfo == null || storage == null)
     779        return null;
    716780
    717781    try
     
    756820  public boolean gotPiece(Peer peer, int piece, byte[] bs)
    757821  {
     822    if (metainfo == null || storage == null)
     823        return true;
    758824    if (halted) {
    759825      _log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
     
    9521018   */
    9531019  public PartialPiece getPartialPiece(Peer peer, BitField havePieces) {
     1020      if (metainfo == null)
     1021          return null;
    9541022      synchronized(wantedPieces) {
    9551023          // sorts by remaining bytes, least first
     
    10581126  }
    10591127
     1128  /**
     1129   *  PeerListener callback
     1130   *  @since 0.8.4
     1131   */
     1132  public void gotExtension(Peer peer, int id, byte[] bs) {
     1133      if (_log.shouldLog(Log.DEBUG))
     1134          _log.debug("Got extension message " + id + " from " + peer);
     1135      // basic handling done in PeerState... here we just check if we are done
     1136      if (metainfo == null && id == ExtensionHandler.ID_METADATA) {
     1137          synchronized (magnetState) {
     1138              if (magnetState.isComplete()) {
     1139                  if (_log.shouldLog(Log.WARN))
     1140                      _log.warn("Got completed metainfo via extension");
     1141                  metainfo = magnetState.getMetaInfo();
     1142                  listener.gotMetaInfo(this, metainfo);
     1143              }
     1144          }
     1145      } else if (id == ExtensionHandler.ID_HANDSHAKE) {
     1146          try {
     1147              if (peer.getHandshakeMap().get("m").getMap().get(ExtensionHandler.TYPE_PEX) != null) {
     1148                  List<Peer> pList = peerList();
     1149                  pList.remove(peer);
     1150                  ExtensionHandler.sendPEX(peer, pList);
     1151              }
     1152          } catch (Exception e) {
     1153              // NPE, no map
     1154          }
     1155      }
     1156  }
     1157
     1158  /**
     1159   *  Sets the storage after transition out of magnet mode
     1160   *  Snark calls this after we call gotMetaInfo()
     1161   *  @since 0.8.4
     1162   */
     1163  public void setStorage(Storage stg) {
     1164      storage = stg;
     1165      setWantedPieces();
     1166      // ok we should be in business
     1167      for (Peer p : peers) {
     1168          p.setMetaInfo(metainfo);
     1169      }
     1170  }
     1171
     1172  /**
     1173   *  PeerListener callback
     1174   *  Tell the DHT to ping it, this will get back the node info
     1175   *  @since 0.8.4
     1176   */
     1177  public void gotPort(Peer peer, int port) {
     1178      DHT dht = _util.getDHT();
     1179      if (dht != null)
     1180          dht.ping(peer.getDestination(), port);
     1181  }
     1182
     1183  /**
     1184   *  PeerListener callback
     1185   *  @since 0.8.4
     1186   */
     1187  public void gotPeers(Peer peer, List<PeerID> peers) {
     1188      // spin off thread or timer task to do a new Peer() and an addPeer() for each one
     1189  }
     1190
    10601191  /** Return number of allowed uploaders for this torrent.
    10611192   ** Check with Snark to see if we are over the total upload limit.
     
    10731204  }
    10741205
     1206  /**
     1207   *  @return current
     1208   *  @since 0.8.4
     1209   */
     1210  public int getUploaders() {
     1211      return uploaders;
     1212  }
     1213
    10751214  public boolean overUpBWLimit()
    10761215  {
  • apps/i2psnark/java/src/org/klomp/snark/PeerListener.java

    r65c61864 rb55bfd03  
    180180   */
    181181  PartialPiece getPartialPiece(Peer peer, BitField havePieces);
     182
     183  /**
     184   * Called when an extension message is received.
     185   *
     186   * @param peer the Peer that got the message.
     187   * @param id the message ID
     188   * @param bs the message payload
     189   * @since 0.8.4
     190   */
     191  void gotExtension(Peer peer, int id, byte[] bs);
     192
     193  /**
     194   * Called when a port message is received.
     195   *
     196   * @param peer the Peer that got the message.
     197   * @param port the port
     198   * @since 0.8.4
     199   */
     200  void gotPort(Peer peer, int port);
     201
     202  /**
     203   * Called when peers are received via PEX
     204   *
     205   * @param peer the Peer that got the message.
     206   * @param pIDList the peer IDs (dest hashes)
     207   * @since 0.8.4
     208   */
     209  void gotPeers(Peer peer, List<PeerID> pIDList);
    182210}
  • apps/i2psnark/java/src/org/klomp/snark/PeerState.java

    r65c61864 rb55bfd03  
    3333import net.i2p.util.Log;
    3434
    35 import org.klomp.snark.bencode.BDecoder;
    36 import org.klomp.snark.bencode.BEValue;
    37 
    3835class PeerState implements DataLoader
    3936{
    4037  private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerState.class);
    4138  private final Peer peer;
     39  /** Fixme, used by Peer.disconnect() to get to the coordinator */
    4240  final PeerListener listener;
    43   private final MetaInfo metainfo;
     41  private MetaInfo metainfo;
    4442
    4543  // Interesting and choking describes whether we are interested in or
     
    5250  boolean interested = false;
    5351  boolean choked = true;
    54 
    55   // Package local for use by Peer.
    56   long downloaded;
    57   long uploaded;
    5852
    5953  /** the pieces the peer has */
     
    7569  private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
    7670
     71  /**
     72   * @param metainfo null if in magnet mode
     73   */
    7774  PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
    7875            PeerConnectionIn in, PeerConnectionOut out)
     
    136133    if (_log.shouldLog(Log.DEBUG))
    137134      _log.debug(peer + " rcv have(" + piece + ")");
     135    // FIXME we will lose these until we get the metainfo
     136    if (metainfo == null)
     137        return;
    138138    // Sanity check
    139139    if (piece < 0 || piece >= metainfo.getPieces())
     
    173173       
    174174        // XXX - Check for weird bitfield and disconnect?
    175         bitfield = new BitField(bitmap, metainfo.getPieces());
    176       }
     175        // FIXME will have to regenerate the bitfield after we know exactly
     176        // how many pieces there are, as we don't know how many spare bits there are.
     177        if (metainfo == null)
     178            bitfield = new BitField(bitmap, bitmap.length * 8);
     179        else
     180            bitfield = new BitField(bitmap, metainfo.getPieces());
     181      }
     182    if (metainfo == null)
     183        return;
    177184    boolean interest = listener.gotBitField(peer, bitfield);
    178185    setInteresting(interest);
     
    192199      _log.debug(peer + " rcv request("
    193200                  + piece + ", " + begin + ", " + length + ") ");
     201    if (metainfo == null)
     202        return;
    194203    if (choking)
    195204      {
     
    274283  void uploaded(int size)
    275284  {
    276     uploaded += size;
     285    peer.uploaded(size);
    277286    listener.uploaded(peer, size);
    278287  }
     
    294303  {
    295304    int size = req.len;
    296     downloaded += size;
     305    peer.downloaded(size);
    297306    listener.downloaded(peer, size);
    298307
     
    315324            if (_log.shouldLog(Log.WARN))
    316325              _log.warn("Got BAD " + req.piece + " from " + peer);
    317             // XXX ARGH What now !?!
    318             // FIXME Why would we set downloaded to 0?
    319             downloaded = 0;
    320326          }
    321327      }
     
    361367                      + begin + ", " + length + "' received from "
    362368                      + peer);
    363         downloaded = 0; // XXX - punishment?
    364369        return null;
    365370      }
     
    386391                          + length + "' received from "
    387392                          + peer);
    388             downloaded = 0; // XXX - punishment?
    389393            return null;
    390394          }
     
    486490  void extensionMessage(int id, byte[] bs)
    487491  {
    488       if (id == 0) {
    489           InputStream is = new ByteArrayInputStream(bs);
    490           try {
    491               BDecoder dec = new BDecoder(is);
    492               BEValue bev = dec.bdecodeMap();
    493               Map map = bev.getMap();
    494               if (_log.shouldLog(Log.DEBUG))
    495                   _log.debug("Got extension handshake message " + bev.toString());
    496           } catch (Exception e) {
    497               if (_log.shouldLog(Log.DEBUG))
    498                   _log.debug("Failed extension decode", e);
    499           }
     492      ExtensionHandler.handleMessage(peer, listener, id, bs);
     493      // Peer coord will get metadata from MagnetState,
     494      // verify, and then call gotMetaInfo()
     495      listener.gotExtension(peer, id, bs);
     496  }
     497
     498  /**
     499   *  Switch from magnet mode to normal mode
     500   *  @since 0.8.4
     501   */
     502  public void setMetaInfo(MetaInfo meta) {
     503      BitField oldBF = bitfield;
     504      if (oldBF != null) {
     505          if (oldBF.size() != meta.getPieces())
     506              // fix bitfield, it was too big by 1-7 bits
     507              bitfield = new BitField(oldBF.getFieldBytes(), meta.getPieces());
     508          // else no extra
    500509      } else {
    501           if (_log.shouldLog(Log.DEBUG))
    502               _log.debug("Got extended message type: " + id + " length: " + bs.length);
    503       }
     510          // it will be initialized later
     511          //bitfield = new BitField(meta.getPieces());
     512      }
     513      metainfo = meta;
     514      if (bitfield.count() > 0)
     515          setInteresting(true);
     516  }
     517
     518  /** @since 0.8.4 */
     519  void portMessage(int port)
     520  {
     521      listener.gotPort(peer, port);
    504522  }
    505523
     
    619637    // no bitfield yet? nothing to request then.
    620638    if (bitfield == null)
     639        return;
     640    if (metainfo == null)
    621641        return;
    622642    boolean more_pieces = true;
  • apps/i2psnark/java/src/org/klomp/snark/Piece.java

    r65c61864 rb55bfd03  
    3636    @Override
    3737    public boolean equals(Object o) {
     38        if (o == null) return false;
    3839        if (o instanceof Piece) {
    39             if (o == null) return false;
    4040            return this.id == ((Piece)o).id;
    4141        }
  • apps/i2psnark/java/src/org/klomp/snark/Snark.java

    r65c61864 rb55bfd03  
    2727import java.io.InputStream;
    2828import java.io.InputStreamReader;
     29import java.util.Collections;
    2930import java.util.Iterator;
    3031import java.util.List;
     
    108109              System.out.println("OOM in the OOM");
    109110          }
    110           System.exit(0);
     111          //System.exit(0);
    111112      }
    112113     
    113114  }
    114115 
     116/******** No, not maintaining a command-line client
     117
    115118  public static void main(String[] args)
    116119  {
     
    236239  }
    237240
     241***********/
     242
    238243  public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections";
    239   public String torrent;
    240   public MetaInfo meta;
    241   public Storage storage;
    242   public PeerCoordinator coordinator;
    243   public ConnectionAcceptor acceptor;
    244   public TrackerClient trackerclient;
    245   public String rootDataDir = ".";
    246   public CompleteListener completeListener;
    247   public boolean stopped;
    248   byte[] id;
    249   public I2PSnarkUtil _util;
    250   private PeerCoordinatorSet _peerCoordinatorSet;
     244
     245  /** most of these used to be public, use accessors below instead */
     246  private String torrent;
     247  private MetaInfo meta;
     248  private Storage storage;
     249  private PeerCoordinator coordinator;
     250  private ConnectionAcceptor acceptor;
     251  private TrackerClient trackerclient;
     252  private String rootDataDir = ".";
     253  private final CompleteListener completeListener;
     254  private boolean stopped;
     255  private byte[] id;
     256  private byte[] infoHash;
     257  private final I2PSnarkUtil _util;
     258  private final PeerCoordinatorSet _peerCoordinatorSet;
     259  private String trackerProblems;
     260  private int trackerSeenPeers;
     261
    251262
    252263  /** from main() via parseArguments() single torrent */
     
    307318    activity = "Network setup";
    308319
    309     // "Taking Three as the subject to reason about--
    310     // A convenient number to state--
    311     // We add Seven, and Ten, and then multiply out
    312     // By One Thousand diminished by Eight.
    313     //
    314     // "The result we proceed to divide, as you see,
    315     // By Nine Hundred and Ninety Two:
    316     // Then subtract Seventeen, and the answer must be
    317     // Exactly and perfectly true.
    318 
    319     // Create a new ID and fill it with something random.  First nine
    320     // zeros bytes, then three bytes filled with snark and then
    321     // sixteen random bytes.
    322     byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
    323     id = new byte[20];
    324     Random random = I2PAppContext.getGlobalContext().random();
    325     int i;
    326     for (i = 0; i < 9; i++)
    327       id[i] = 0;
    328     id[i++] = snark;
    329     id[i++] = snark;
    330     id[i++] = snark;
    331     while (i < 20)
    332       id[i++] = (byte)random.nextInt(256);
    333 
     320    id = generateID();
    334321    debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
    335322
     
    374361          }
    375362        meta = new MetaInfo(new BDecoder(in));
     363        infoHash = meta.getInfoHash();
    376364      }
    377365    catch(IOException ioe)
     
    407395        else
    408396          fatal("Cannot open '" + torrent + "'", ioe);
     397      } catch (OutOfMemoryError oom) {
     398          fatal("ERROR - Out of memory, cannot create torrent " + torrent + ": " + oom.getMessage());
    409399      } finally {
    410400          if (in != null)
     
    458448        startTorrent();
    459449  }
     450
     451  /**
     452   *  multitorrent, magnet
     453   *
     454   *  @param torrent a fake name for now (not a file name)
     455   *  @param ih 20-byte info hash
     456   *  @since 0.8.4
     457   */
     458  public Snark(I2PSnarkUtil util, String torrent, byte[] ih,
     459        CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
     460        ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
     461  {
     462    completeListener = complistener;
     463    _util = util;
     464    _peerCoordinatorSet = peerCoordinatorSet;
     465    acceptor = connectionAcceptor;
     466    this.torrent = torrent;
     467    this.infoHash = ih;
     468    this.rootDataDir = rootDir;
     469    stopped = true;
     470    id = generateID();
     471
     472    // All we have is an infoHash
     473    // meta remains null
     474    // storage remains null
     475
     476    if (start)
     477        startTorrent();
     478  }
     479
     480  private static byte[] generateID() {
     481    // "Taking Three as the subject to reason about--
     482    // A convenient number to state--
     483    // We add Seven, and Ten, and then multiply out
     484    // By One Thousand diminished by Eight.
     485    //
     486    // "The result we proceed to divide, as you see,
     487    // By Nine Hundred and Ninety Two:
     488    // Then subtract Seventeen, and the answer must be
     489    // Exactly and perfectly true.
     490
     491    // Create a new ID and fill it with something random.  First nine
     492    // zeros bytes, then three bytes filled with snark and then
     493    // sixteen random bytes.
     494    byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
     495    byte[] rv = new byte[20];
     496    Random random = I2PAppContext.getGlobalContext().random();
     497    int i;
     498    for (i = 0; i < 9; i++)
     499      rv[i] = 0;
     500    rv[i++] = snark;
     501    rv[i++] = snark;
     502    rv[i++] = snark;
     503    while (i < 20)
     504      rv[i++] = (byte)random.nextInt(256);
     505    return rv;
     506  }
     507
    460508  /**
    461509   * Start up contacting peers and querying the tracker
     
    474522        debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
    475523        activity = "Collecting pieces";
    476         coordinator = new PeerCoordinator(_util, id, meta, storage, this, this);
     524        coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
    477525        if (_peerCoordinatorSet != null) {
    478526            // multitorrent
     
    487535            acceptor = new ConnectionAcceptor(_util, serversocket, new PeerAcceptor(coordinator));
    488536        }
    489         trackerclient = new TrackerClient(_util, meta, coordinator);
     537        // TODO pass saved closest DHT nodes to the tracker? or direct to the coordinator?
     538        trackerclient = new TrackerClient(_util, meta, coordinator, this);
    490539    }
    491540
     
    497546        if (_peerCoordinatorSet != null)
    498547            _peerCoordinatorSet.remove(coordinator);
    499         PeerCoordinator newCoord = new PeerCoordinator(_util, coordinator.getID(), coordinator.getMetaInfo(),
    500                                                        coordinator.getStorage(), coordinator.getListener(), this);
     548        PeerCoordinator newCoord = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
    501549        if (_peerCoordinatorSet != null)
    502550            _peerCoordinatorSet.add(newCoord);
     
    507555        trackerclient.start();
    508556    } else if (trackerclient.halted() || coordinatorChanged) {
    509         try
    510           {
    511             storage.reopen(rootDataDir);
    512           }
    513         catch (IOException ioe)
    514           {
    515             try { storage.close(); } catch (IOException ioee) {
    516                 ioee.printStackTrace();
    517             }
    518             fatal("Could not reopen storage", ioe);
    519           }
    520         TrackerClient newClient = new TrackerClient(_util, coordinator.getMetaInfo(), coordinator);
     557        if (storage != null) {
     558            try {
     559                 storage.reopen(rootDataDir);
     560             }   catch (IOException ioe) {
     561                 try { storage.close(); } catch (IOException ioee) {
     562                     ioee.printStackTrace();
     563                 }
     564                 fatal("Could not reopen storage", ioe);
     565             }
     566        }
     567        TrackerClient newClient = new TrackerClient(_util, meta, coordinator, this);
    521568        if (!trackerclient.halted())
    522569            trackerclient.halt();
     
    554601  }
    555602
    556   static Snark parseArguments(String[] args)
     603  private static Snark parseArguments(String[] args)
    557604  {
    558605    return parseArguments(args, null, null);
    559606  }
     607
     608    // Accessors
     609
     610    /**
     611     *  @return file name of .torrent file (should be full absolute path), or a fake name if in magnet mode.
     612     *  @since 0.8.4
     613     */
     614    public String getName() {
     615        return torrent;
     616    }
     617
     618    /**
     619     *  @return base name of torrent [filtered version of getMetaInfo.getName()], or a fake name if in magnet mode
     620     *  @since 0.8.4
     621     */
     622    public String getBaseName() {
     623        if (storage != null)
     624            return storage.getBaseName();
     625        return torrent;
     626    }
     627
     628    /**
     629     *  @return always will be valid even in magnet mode
     630     *  @since 0.8.4
     631     */
     632    public byte[] getID() {
     633        return id;
     634    }
     635
     636    /**
     637     *  @return always will be valid even in magnet mode
     638     *  @since 0.8.4
     639     */
     640    public byte[] getInfoHash() {
     641        // should always be the same
     642        if (meta != null)
     643            return meta.getInfoHash();
     644        return infoHash;
     645    }
     646
     647    /**
     648     *  @return may be null if in magnet mode
     649     *  @since 0.8.4
     650     */
     651    public MetaInfo getMetaInfo() {
     652        return meta;
     653    }
     654
     655    /**
     656     *  @return may be null if in magnet mode
     657     *  @since 0.8.4
     658     */
     659    public Storage getStorage() {
     660        return storage;
     661    }
     662
     663    /**
     664     *  @since 0.8.4
     665     */
     666    public boolean isStopped() {
     667        return stopped;
     668    }
     669
     670    /**
     671     *  @since 0.8.4
     672     */
     673    public long getDownloadRate() {
     674        PeerCoordinator coord = coordinator;
     675        if (coord != null)
     676            return coord.getDownloadRate();
     677        return 0;
     678    }
     679
     680    /**
     681     *  @since 0.8.4
     682     */
     683    public long getUploadRate() {
     684        PeerCoordinator coord = coordinator;
     685        if (coord != null)
     686            return coord.getUploadRate();
     687        return 0;
     688    }
     689
     690    /**
     691     *  @since 0.8.4
     692     */
     693    public long getDownloaded() {
     694        PeerCoordinator coord = coordinator;
     695        if (coord != null)
     696            return coord.getDownloaded();
     697        return 0;
     698    }
     699
     700    /**
     701     *  @since 0.8.4
     702     */
     703    public long getUploaded() {
     704        PeerCoordinator coord = coordinator;
     705        if (coord != null)
     706            return coord.getUploaded();
     707        return 0;
     708    }
     709
     710    /**
     711     *  @since 0.8.4
     712     */
     713    public int getPeerCount() {
     714        PeerCoordinator coord = coordinator;
     715        if (coord != null)
     716            return coord.getPeerCount();
     717        return 0;
     718    }
     719
     720    /**
     721     *  @since 0.8.4
     722     */
     723    public List<Peer> getPeerList() {
     724        PeerCoordinator coord = coordinator;
     725        if (coord != null)
     726            return coord.peerList();
     727        return Collections.EMPTY_LIST;
     728    }
     729
     730    /**
     731     *  @return String returned from tracker, or null if no error
     732     *  @since 0.8.4
     733     */
     734    public String getTrackerProblems() {
     735        return trackerProblems;
     736    }
     737
     738    /**
     739     *  @param p tracker error string or null
     740     *  @since 0.8.4
     741     */
     742    public void setTrackerProblems(String p) {
     743        trackerProblems = p;
     744    }
     745
     746    /**
     747     *  @return count returned from tracker
     748     *  @since 0.8.4
     749     */
     750    public int getTrackerSeenPeers() {
     751        return trackerSeenPeers;
     752    }
     753
     754    /**
     755     *  @since 0.8.4
     756     */
     757    public void setTrackerSeenPeers(int p) {
     758        trackerSeenPeers = p;
     759    }
     760
     761    /**
     762     *  @since 0.8.4
     763     */
     764    public void updatePiecePriorities() {
     765        PeerCoordinator coord = coordinator;
     766        if (coord != null)
     767            coord.updatePiecePriorities();
     768    }
     769
     770    /**
     771     *  @return total of all torrent files, or total of metainfo file if fetching magnet, or -1
     772     *  @since 0.8.4
     773     */
     774    public long getTotalLength() {
     775        if (meta != null)
     776            return meta.getTotalLength();
     777        // FIXME else return metainfo length if available
     778        return -1;
     779    }
     780
     781    /**
     782     *  @return number of pieces still needed (magnet mode or not), or -1 if unknown
     783     *  @since 0.8.4
     784     */
     785    public long getNeeded() {
     786        if (storage != null)
     787            return storage.needed();
     788        if (meta != null)
     789            // FIXME subtract chunks we have
     790            return meta.getTotalLength();
     791        // FIXME fake
     792        return -1;
     793    }
     794
     795    /**
     796     *  @param p the piece number
     797     *  @return metainfo piece length or 16K if fetching magnet
     798     *  @since 0.8.4
     799     */
     800    public int getPieceLength(int p) {
     801        if (meta != null)
     802            return meta.getPieceLength(p);
     803        return 16*1024;
     804    }
     805
     806    /**
     807     *  @return number of pieces
     808     *  @since 0.8.4
     809     */
     810    public int getPieces() {
     811        if (meta != null)
     812            return meta.getPieces();
     813        // FIXME else return metainfo pieces if available
     814        return -1;
     815    }
     816
     817    /**
     818     *  @return true if restarted
     819     *  @since 0.8.4
     820     */
     821    public boolean restartAcceptor() {
     822        if (acceptor == null)
     823            return false;
     824        acceptor.restart();
     825        return true;
     826    }
    560827
    561828  /**
     
    565832   * passed to all components that take one.
    566833   */
    567   static Snark parseArguments(String[] args,
     834  private static Snark parseArguments(String[] args,
    568835                              StorageListener slistener,
    569836                              CoordinatorListener clistener)
     
    714981    System.out.println
    715982      ("         \tor (with --share) a file to share.");
    716     System.exit(-1);
    717983  }
    718984
     
    720986   * Aborts program abnormally.
    721987   */
    722   public void fatal(String s)
     988  private void fatal(String s)
    723989  {
    724990    fatal(s, null);
     
    728994   * Aborts program abnormally.
    729995   */
    730   public void fatal(String s, Throwable t)
     996  private void fatal(String s, Throwable t)
    731997  {
    732998    _util.debug(s, ERROR, t);
     
    7521018  }
    7531019 
    754   boolean allocating = false;
     1020  /**
     1021   * Called when the PeerCoordinator got the MetaInfo via magnet.
     1022   * CoordinatorListener.
     1023   * Create the storage, tell SnarkManager, and give the storage
     1024   * back to the coordinator.
     1025   *
     1026   * @throws RuntimeException via fatal()
     1027   * @since 0.8.4
     1028   */
     1029  public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) {
     1030      meta = metainfo;
     1031      try {
     1032          storage = new Storage(_util, meta, this);
     1033          storage.check(rootDataDir);
     1034          if (completeListener != null) {
     1035              String newName = completeListener.gotMetaInfo(this);
     1036              if (newName != null)
     1037                  torrent = newName;
     1038              // else some horrible problem
     1039          }
     1040          coordinator.setStorage(storage);
     1041      } catch (IOException ioe) {
     1042          if (storage != null) {
     1043              try { storage.close(); } catch (IOException ioee) {}
     1044          }
     1045          fatal("Could not check or create storage", ioe);
     1046      }
     1047  }
     1048
     1049  private boolean allocating = false;
    7551050  public void storageCreateFile(Storage storage, String name, long length)
    7561051  {
     
    7751070  }
    7761071
    777   boolean allChecked = false;
    778   boolean checking = false;
    779   boolean prechecking = true;
     1072  private boolean allChecked = false;
     1073  private boolean checking = false;
     1074  private boolean prechecking = true;
    7801075  public void storageChecked(Storage storage, int num, boolean checked)
    7811076  {
     
    8221117  }
    8231118
     1119  /** SnarkSnutdown callback unused */
    8241120  public void shutdown()
    8251121  {
    8261122    // Should not be necessary since all non-deamon threads should
    8271123    // have died. But in reality this does not always happen.
    828     System.exit(0);
     1124    //System.exit(0);
    8291125  }
    8301126 
     
    8321128    public void torrentComplete(Snark snark);
    8331129    public void updateStatus(Snark snark);
     1130
     1131    /**
     1132     * We transitioned from magnet mode, we have now initialized our
     1133     * metainfo and storage. The listener should now call getMetaInfo()
     1134     * and save the data to disk.
     1135     *
     1136     * @return the new name for the torrent or null on error
     1137     * @since 0.8.4
     1138     */
     1139    public String gotMetaInfo(Snark snark);
     1140
    8341141    // not really listeners but the easiest way to get back to an optional SnarkManager
    8351142    public long getSavedTorrentTime(Snark snark);
  • apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java

    r65c61864 rb55bfd03  
    66import java.io.FilenameFilter;
    77import java.io.IOException;
     8import java.io.OutputStream;
    89import java.util.ArrayList;
    910import java.util.HashMap;
     
    1718import java.util.TreeMap;
    1819import java.util.Collection;
     20import java.util.concurrent.ConcurrentHashMap;
    1921
    2022import net.i2p.I2PAppContext;
    2123import net.i2p.data.Base64;
    2224import net.i2p.data.DataHelper;
     25import net.i2p.util.ConcurrentHashSet;
     26import net.i2p.util.FileUtil;
    2327import net.i2p.util.I2PAppThread;
    2428import net.i2p.util.Log;
    2529import net.i2p.util.OrderedProperties;
    2630import net.i2p.util.SecureDirectory;
     31import net.i2p.util.SecureFileOutputStream;
    2732
    2833/**
     
    3338    public static SnarkManager instance() { return _instance; }
    3439   
    35     /** map of (canonical) filename of the .torrent file to Snark instance (unsynchronized) */
     40    /**
     41     *  Map of (canonical) filename of the .torrent file to Snark instance.
     42     *  This is a CHM so listTorrentFiles() need not be synced, but
     43     *  all adds, deletes, and the DirMonitor should sync on it.
     44     */
    3645    private final Map<String, Snark> _snarks;
     46    /** used to prevent DirMonitor from deleting torrents that don't have a torrent file yet */
     47    private final Set<String> _magnets;
    3748    private final Object _addSnarkLock;
    3849    private /* FIXME final FIXME */ File _configFile;
     
    5869    public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
    5970    public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
     71    public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
    6072
    6173    private static final String CONFIG_FILE = "i2psnark.config";
     
    7284    public static final int DEFAULT_STARTUP_DELAY = 3;
    7385    private SnarkManager() {
    74         _snarks = new HashMap();
     86        _snarks = new ConcurrentHashMap();
     87        _magnets = new ConcurrentHashSet();
    7588        _addSnarkLock = new Object();
    7689        _context = I2PAppContext.getGlobalContext();
     
    91104        _peerCoordinatorSet = new PeerCoordinatorSet();
    92105        _connectionAcceptor = new ConnectionAcceptor(_util);
    93         int minutes = getStartupDelayMinutes();
    94         _messages.add(_("Adding torrents in {0} minutes", minutes));
    95106        _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true);
    96107        _monitor.start();
     
    237248            }
    238249        }
    239         if (i2cpHost != null) {
    240             _util.setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
    241             if (_log.shouldLog(Log.DEBUG))
    242                 _log.debug("Configuring with I2CP options " + i2cpOpts);
    243         }
     250        _util.setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
     251        if (_log.shouldLog(Log.DEBUG))
     252            _log.debug("Configuring with I2CP options " + i2cpOpts);
    244253        //I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties());
    245254        //String eepHost = _config.getProperty(PROP_EEP_HOST);
     
    253262        if (ot != null)
    254263            _util.setOpenTrackerString(ot);
    255         // FIXME set util use open trackers property somehow
     264        String useOT = _config.getProperty(I2PSnarkUtil.PROP_USE_OPENTRACKERS);
     265        boolean bOT = useOT == null || Boolean.valueOf(useOT).booleanValue();
     266        _util.setUseOpenTrackers(bOT);
    256267        getDataDir().mkdirs();
    257268    }
     
    322333                            changed = true;
    323334                            _config.setProperty(PROP_STARTUP_DELAY, "" + minutes);
    324                             addMessage(_("Startup delay limit changed to {0} minutes", minutes));
     335                            addMessage(_("Startup delay changed to {0}", DataHelper.formatDuration2(minutes * 60 * 1000)));
    325336                        }
    326337
    327338        }
     339        // FIXME do this even if == null
    328340        if (i2cpHost != null) {
    329341            int oldI2CPPort = _util.getI2CPPort();
    330342            String oldI2CPHost = _util.getI2CPHost();
    331343            int port = oldI2CPPort;
    332             try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
     344            if (i2cpPort != null) {
     345                try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
     346            }
    333347            String host = oldI2CPHost;
    334348            Map opts = new HashMap();
     
    360374                for (Iterator iter = names.iterator(); iter.hasNext(); ) {
    361375                    Snark snark = getTorrent((String)iter.next());
    362                     if ( (snark != null) && (!snark.stopped) ) {
     376                    if ( (snark != null) && (!snark.isStopped()) ) {
    363377                        snarksActive = true;
    364378                        break;
     
    369383                    p.putAll(opts);
    370384                    _util.setI2CPConfig(i2cpHost, port, p);
     385                    _util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
    371386                    addMessage(_("I2CP and tunnel changes will take effect after stopping all torrents"));
    372387                    if (_log.shouldLog(Log.DEBUG))
     
    382397                    addMessage(_("I2CP settings changed to {0}", i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")"));
    383398                    _util.setI2CPConfig(i2cpHost, port, p);
     399                    _util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
    384400                    boolean ok = _util.connect();
    385401                    if (!ok) {
     
    399415                            String name = (String)iter.next();
    400416                            Snark snark = getTorrent(name);
    401                             if ( (snark != null) && (snark.acceptor != null) ) {
    402                                 snark.acceptor.restart();
    403                                 addMessage(_("I2CP listener restarted for \"{0}\"", snark.meta.getName()));
     417                            if (snark != null && snark.restartAcceptor()) {
     418                                addMessage(_("I2CP listener restarted for \"{0}\"", snark.getBaseName()));
    404419                            }
    405420                        }
     
    423438            else
    424439                addMessage(_("Disabled open trackers - torrent restart required to take effect."));
     440            _util.setUseOpenTrackers(useOpenTrackers);
    425441            changed = true;
    426442        }
     
    462478    private static final int MAX_FILES_PER_TORRENT = 512;
    463479   
    464     /** set of canonical .torrent filenames that we are dealing with */
    465     public Set<String> listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
     480    /**
     481     *  Set of canonical .torrent filenames that we are dealing with.
     482     *  An unsynchronized copy.
     483     */
     484    public Set<String> listTorrentFiles() {
     485        return new HashSet(_snarks.keySet());
     486    }
    466487
    467488    /**
     
    479500        synchronized (_snarks) {
    480501            for (Snark s : _snarks.values()) {
    481                 if (s.storage.getBaseName().equals(filename))
     502                if (s.getBaseName().equals(filename))
    482503                    return s;
    483504            }
     
    486507    }
    487508
    488     /** @throws RuntimeException via Snark.fatal() */
     509    /**
     510     * Grab the torrent given the info hash
     511     * @return Snark or null
     512     * @since 0.8.4
     513     */
     514    public Snark getTorrentByInfoHash(byte[] infohash) {
     515        synchronized (_snarks) {
     516            for (Snark s : _snarks.values()) {
     517                if (DataHelper.eq(infohash, s.getInfoHash()))
     518                    return s;
     519            }
     520        }
     521        return null;
     522    }
     523
     524    /**
     525     *  Caller must verify this torrent is not already added.
     526     *  @throws RuntimeException via Snark.fatal()
     527     */
    489528    public void addTorrent(String filename) { addTorrent(filename, false); }
    490529
    491     /** @throws RuntimeException via Snark.fatal() */
     530    /**
     531     *  Caller must verify this torrent is not already added.
     532     *  @throws RuntimeException via Snark.fatal()
     533     */
    492534    public void addTorrent(String filename, boolean dontAutoStart) {
    493535        if ((!dontAutoStart) && !_util.connected()) {
     
    539581                    if (!TrackerClient.isValidAnnounce(info.getAnnounce())) {
    540582                        if (_util.shouldUseOpenTrackers() && _util.getOpenTrackers() != null) {
    541                             addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only", info.getName()));
     583                            addMessage(_("Warning - No I2P trackers in \"{0}\", will announce to I2P open trackers and DHT only.", info.getName()));
     584                        } else if (_util.getDHT() != null) {
     585                            addMessage(_("Warning - No I2P trackers in \"{0}\", and open trackers are disabled, will announce to DHT only.", info.getName()));
    542586                        } else {
    543                             addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!", info.getName()));
     587                            addMessage(_("Warning - No I2P trackers in \"{0}\", and DHT and open trackers are disabled, you should enable open trackers or DHT before starting the torrent.", info.getName()));
    544588                            dontAutoStart = true;
    545589                        }
    546590                    }
    547                     String rejectMessage = locked_validateTorrent(info);
     591                    String rejectMessage = validateTorrent(info);
    548592                    if (rejectMessage != null) {
    549593                        sfile.delete();
     
    551595                        return;
    552596                    } else {
     597                        // TODO load saved closest DHT nodes and pass to the Snark ?
     598                        // This may take a LONG time
    553599                        torrent = new Snark(_util, filename, null, -1, null, null, this,
    554600                                            _peerCoordinatorSet, _connectionAcceptor,
    555601                                            false, dataDir.getPath());
    556602                        loadSavedFilePriorities(torrent);
    557                         torrent.completeListener = this;
    558603                        synchronized (_snarks) {
    559604                            _snarks.put(filename, torrent);
     
    565610                        sfile.delete();
    566611                    return;
     612                } catch (OutOfMemoryError oom) {
     613                    addMessage(_("ERROR - Out of memory, cannot create torrent from {0}", sfile.getName()) + ": " + oom.getMessage());
    567614                } finally {
    568615                    if (fis != null) try { fis.close(); } catch (IOException ioe) {}
     
    573620        }
    574621        // ok, snark created, now lets start it up or configure it further
    575         File f = new File(filename);
    576622        if (!dontAutoStart && shouldAutoStart()) {
    577623            torrent.startTorrent();
    578             addMessage(_("Torrent added and started: \"{0}\"", torrent.storage.getBaseName()));
     624            addMessage(_("Torrent added and started: \"{0}\"", torrent.getBaseName()));
    579625        } else {
    580             addMessage(_("Torrent added: \"{0}\"", torrent.storage.getBaseName()));
    581         }
    582     }
    583    
    584     /**
    585      * Get the timestamp for a torrent from the config file
     626            addMessage(_("Torrent added: \"{0}\"", torrent.getBaseName()));
     627        }
     628    }
     629   
     630    /**
     631     * Add a torrent with the info hash alone (magnet / maggot)
     632     *
     633     * @param name hex or b32 name from the magnet link
     634     * @param ih 20 byte info hash
     635     * @throws RuntimeException via Snark.fatal()
     636     * @since 0.8.4
     637     */
     638    public void addMagnet(String name, byte[] ih, boolean updateStatus) {
     639        Snark torrent = new Snark(_util, name, ih, this,
     640                                  _peerCoordinatorSet, _connectionAcceptor,
     641                                  false, getDataDir().getPath());
     642
     643        synchronized (_snarks) {
     644            Snark snark = getTorrentByInfoHash(ih);
     645            if (snark != null) {
     646                addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
     647                return;
     648            }
     649            // Tell the dir monitor not to delete us
     650            _magnets.add(name);
     651            if (updateStatus)
     652                saveMagnetStatus(ih);
     653            _snarks.put(name, torrent);
     654        }
     655        if (shouldAutoStart()) {
     656            torrent.startTorrent();
     657            addMessage(_("Fetching {0}", name));
     658            boolean haveSavedPeers = false;
     659            if ((!util().connected()) && !haveSavedPeers) {
     660                addMessage(_("We have no saved peers and no other torrents are running. " +
     661                             "Fetch of {0} will not succeed until you start another torrent.", name));
     662            }
     663        } else {
     664            addMessage(_("Adding {0}", name));
     665      }
     666    }
     667
     668    /**
     669     * Stop and delete a torrent running in magnet mode
     670     *
     671     * @param snark a torrent with a fake file name ("Magnet xxxx")
     672     * @since 0.8.4
     673     */
     674    public void deleteMagnet(Snark snark) {
     675        synchronized (_snarks) {
     676            _snarks.remove(snark.getName());
     677        }
     678        snark.stopTorrent();
     679        _magnets.remove(snark.getName());
     680        removeMagnetStatus(snark.getInfoHash());
     681    }
     682
     683    /**
     684     * Add a torrent from a MetaInfo. Save the MetaInfo data to filename.
     685     * Holds the snarks lock to prevent interference from the DirMonitor.
     686     * This verifies that a torrent with this infohash is not already added.
     687     * This may take a LONG time to create or check the storage.
     688     *
     689     * @param metainfo the metainfo for the torrent
     690     * @param bitfield the current completion status of the torrent
     691     * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent
     692     *                 Must be a filesystem-safe name.
     693     * @throws RuntimeException via Snark.fatal()
     694     * @since 0.8.4
     695     */
     696    public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, boolean dontAutoStart) throws IOException {
     697        // prevent interference by DirMonitor
     698        synchronized (_snarks) {
     699            Snark snark = getTorrentByInfoHash(metainfo.getInfoHash());
     700            if (snark != null) {
     701                addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
     702                return;
     703            }
     704            // so addTorrent won't recheck
     705            saveTorrentStatus(metainfo, bitfield, null); // no file priorities
     706            try {
     707                locked_writeMetaInfo(metainfo, filename);
     708                // hold the lock for a long time
     709                addTorrent(filename, dontAutoStart);
     710            } catch (IOException ioe) {
     711                addMessage(_("Failed to copy torrent file to {0}", filename));
     712                _log.error("Failed to write torrent file", ioe);
     713            }
     714        }
     715    }
     716
     717    /**
     718     * Add a torrent from a file not in the torrent directory. Copy the file to filename.
     719     * Holds the snarks lock to prevent interference from the DirMonitor.
     720     * Caller must verify this torrent is not already added.
     721     * This may take a LONG time to create or check the storage.
     722     *
     723     * @param fromfile where the file is now, presumably in a temp directory somewhere
     724     * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent
     725     *                 Must be a filesystem-safe name.
     726     * @throws RuntimeException via Snark.fatal()
     727     * @since 0.8.4
     728     */
     729    public void copyAndAddTorrent(File fromfile, String filename) throws IOException {
     730        // prevent interference by DirMonitor
     731        synchronized (_snarks) {
     732            boolean success = FileUtil.copy(fromfile.getAbsolutePath(), filename, false);
     733            if (!success) {
     734                addMessage(_("Failed to copy torrent file to {0}", filename));
     735                _log.error("Failed to write torrent file to " + filename);
     736                return;
     737            }
     738            SecureFileOutputStream.setPerms(new File(filename));
     739            // hold the lock for a long time
     740            addTorrent(filename);
     741         }
     742    }
     743
     744    /**
     745     * Write the metainfo to the file, caller must hold the snarks lock
     746     * to prevent interference from the DirMonitor.
     747     *
     748     * @param metainfo The metainfo for the torrent
     749     * @param filename The absolute path to save the metainfo to, generally ending in ".torrent".
     750     *                 Must be a filesystem-safe name.
     751     * @since 0.8.4
     752     */
     753    private void locked_writeMetaInfo(MetaInfo metainfo, String filename) throws IOException {
     754        // prevent interference by DirMonitor
     755        File file = new File(filename);
     756        if (file.exists())
     757            throw new IOException("Cannot overwrite an existing .torrent file: " + file.getPath());
     758        OutputStream out = null;
     759        try {
     760            out = new SecureFileOutputStream(filename);
     761            out.write(metainfo.getTorrentData());
     762        } catch (IOException ioe) {
     763            // remove any partial
     764            file.delete();
     765            throw ioe;
     766        } finally {
     767            try {
     768                if (out == null)
     769                    out.close();
     770            } catch (IOException ioe) {}
     771        }
     772    }
     773
     774    /**
     775     * Get the timestamp for a torrent from the config file.
     776     * A Snark.CompleteListener method.
    586777     */
    587778    public long getSavedTorrentTime(Snark snark) {
    588         MetaInfo metainfo = snark.meta;
    589         byte[] ih = metainfo.getInfoHash();
     779        byte[] ih = snark.getInfoHash();
    590780        String infohash = Base64.encode(ih);
    591781        infohash = infohash.replace('=', '$');
     
    604794     * Get the saved bitfield for a torrent from the config file.
    605795     * Convert "." to a full bitfield.
     796     * A Snark.CompleteListener method.
    606797     */
    607798    public BitField getSavedTorrentBitField(Snark snark) {
    608         MetaInfo metainfo = snark.meta;
    609         byte[] ih = metainfo.getInfoHash();
     799        MetaInfo metainfo = snark.getMetaInfo();
     800        if (metainfo == null)
     801            return null;
     802        byte[] ih = snark.getInfoHash();
    610803        String infohash = Base64.encode(ih);
    611804        infohash = infohash.replace('=', '$');
     
    637830     */
    638831    public void loadSavedFilePriorities(Snark snark) {
    639         MetaInfo metainfo = snark.meta;
     832        MetaInfo metainfo = snark.getMetaInfo();
     833        Storage storage = snark.getStorage();
     834        if (metainfo == null || storage == null)
     835            return;
    640836        if (metainfo.getFiles() == null)
    641837            return;
    642         byte[] ih = metainfo.getInfoHash();
     838        byte[] ih = snark.getInfoHash();
    643839        String infohash = Base64.encode(ih);
    644840        infohash = infohash.replace('=', '$');
     
    656852            }
    657853        }
    658         snark.storage.setFilePriorities(rv);
     854        storage.setFilePriorities(rv);
    659855    }
    660856   
     
    667863     * The status is either a bitfield converted to Base64 or "." for a completed
    668864     * torrent to save space in the config file and in memory.
     865     *
     866     * @param bitfield non-null
    669867     * @param priorities may be null
    670868     */
     
    710908        }
    711909
     910        // TODO save closest DHT nodes too
     911
    712912        saveConfig();
    713913    }
     
    727927   
    728928    /**
     929     *  Just remember we have it
     930     *  @since 0.8.4
     931     */
     932    public void saveMagnetStatus(byte[] ih) {
     933        String infohash = Base64.encode(ih);
     934        infohash = infohash.replace('=', '$');
     935        _config.setProperty(PROP_META_MAGNET_PREFIX + infohash, ".");
     936        saveConfig();
     937    }
     938   
     939    /**
     940     *  Remove the magnet marker from the config file.
     941     *  @since 0.8.4
     942     */
     943    public void removeMagnetStatus(byte[] ih) {
     944        String infohash = Base64.encode(ih);
     945        infohash = infohash.replace('=', '$');
     946        _config.remove(PROP_META_MAGNET_PREFIX + infohash);
     947        saveConfig();
     948    }
     949   
     950    /**
     951     *  Does not really delete on failure, that's the caller's responsibility.
    729952     *  Warning - does not validate announce URL - use TrackerClient.isValidAnnounce()
    730      */
    731     private String locked_validateTorrent(MetaInfo info) throws IOException {
     953     *  @return failure message or null on success
     954     */
     955    private String validateTorrent(MetaInfo info) {
    732956        List files = info.getFiles();
    733957        if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
     
    7781002        }
    7791003        if (torrent != null) {
    780             boolean wasStopped = torrent.stopped;
     1004            boolean wasStopped = torrent.isStopped();
    7811005            torrent.stopTorrent();
    7821006            if (remaining == 0) {
     
    7851009                ////_util.
    7861010            }
    787             String name;
    788             if (torrent.storage != null) {
    789                 name = torrent.storage.getBaseName();
    790             } else {
    791                 name = sfile.getName();
    792             }
    7931011            if (!wasStopped)
    794                 addMessage(_("Torrent stopped: \"{0}\"", name));
     1012                addMessage(_("Torrent stopped: \"{0}\"", torrent.getBaseName()));
    7951013        }
    7961014        return torrent;
    7971015    }
     1016
     1017    /**
     1018     * Stop the torrent, leaving it on the list of torrents unless told to remove it
     1019     * @since 0.8.4
     1020     */
     1021    public void stopTorrent(Snark torrent, boolean shouldRemove) {
     1022        if (shouldRemove) {
     1023            synchronized (_snarks) {
     1024                _snarks.remove(torrent.getName());
     1025            }
     1026        }
     1027        boolean wasStopped = torrent.isStopped();
     1028        torrent.stopTorrent();
     1029        if (!wasStopped)
     1030            addMessage(_("Torrent stopped: \"{0}\"", torrent.getBaseName()));
     1031    }
     1032
    7981033    /**
    7991034     * Stop the torrent and delete the torrent file itself, but leaving the data
    8001035     * behind.
     1036     * Holds the snarks lock to prevent interference from the DirMonitor.
    8011037     */
    8021038    public void removeTorrent(String filename) {
    803         Snark torrent = stopTorrent(filename, true);
    804         if (torrent != null) {
     1039        Snark torrent;
     1040        // prevent interference by DirMonitor
     1041        synchronized (_snarks) {
     1042            torrent = stopTorrent(filename, true);
     1043            if (torrent == null)
     1044                return;
    8051045            File torrentFile = new File(filename);
    8061046            torrentFile.delete();
    807             String name;
    808             if (torrent.storage != null) {
    809                 removeTorrentStatus(torrent.storage.getMetaInfo());
    810                 name = torrent.storage.getBaseName();
    811             } else {
    812                 name = torrentFile.getName();
    813             }
    814             addMessage(_("Torrent removed: \"{0}\"", name));
    815         }
     1047        }
     1048        Storage storage = torrent.getStorage();
     1049        if (storage != null)
     1050            removeTorrentStatus(storage.getMetaInfo());
     1051        addMessage(_("Torrent removed: \"{0}\"", torrent.getBaseName()));
    8161052    }
    8171053   
    8181054    private class DirMonitor implements Runnable {
    8191055        public void run() {
    820             try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {}
    821             // the first message was a "We are starting up in 1m"
    822             synchronized (_messages) {
    823                 if (_messages.size() == 1)
    824                     _messages.remove(0);
     1056            // don't bother delaying if auto start is false
     1057            long delay = 60 * 1000 * getStartupDelayMinutes();
     1058            if (delay > 0 && shouldAutoStart()) {
     1059                _messages.add(_("Adding torrents in {0}", DataHelper.formatDuration2(delay)));
     1060                try { Thread.sleep(delay); } catch (InterruptedException ie) {}
     1061                // the first message was a "We are starting up in 1m"
     1062                synchronized (_messages) {
     1063                    if (_messages.size() == 1)
     1064                        _messages.remove(0);
     1065                }
    8251066            }
    8261067
     
    8281069            // although the user will see the default until then
    8291070            getBWLimit();
     1071            boolean doMagnets = true;
    8301072            while (true) {
    8311073                File dir = getDataDir();
     
    8331075                    _log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
    8341076                try {
    835                     monitorTorrents(dir);
     1077                    // Don't let this interfere with .torrent files being added or deleted
     1078                    synchronized (_snarks) {
     1079                        monitorTorrents(dir);
     1080                    }
    8361081                } catch (Exception e) {
    8371082                    _log.error("Error in the DirectoryMonitor", e);
    8381083                }
     1084                if (doMagnets) {
     1085                    addMagnets();
     1086                    doMagnets = false;
     1087                }
    8391088                try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
    8401089            }
     
    8421091    }
    8431092   
    844     /** two listeners */
     1093    // Begin Snark.CompleteListeners
     1094
     1095    /**
     1096     * A Snark.CompleteListener method.
     1097     */
    8451098    public void torrentComplete(Snark snark) {
     1099        MetaInfo meta = snark.getMetaInfo();
     1100        Storage storage = snark.getStorage();
     1101        if (meta == null || storage == null)
     1102            return;
    8461103        StringBuilder buf = new StringBuilder(256);
    847         buf.append("<a href=\"/i2psnark/").append(snark.storage.getBaseName());
    848         if (snark.meta.getFiles() != null)
     1104        buf.append("<a href=\"/i2psnark/").append(storage.getBaseName());
     1105        if (meta.getFiles() != null)
    8491106            buf.append('/');
    850         buf.append("\">").append(snark.storage.getBaseName()).append("</a>");
    851         long len = snark.meta.getTotalLength();
     1107        buf.append("\">").append(storage.getBaseName()).append("</a>");
    8521108        addMessage(_("Download finished: {0}", buf.toString())); //  + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')');
    8531109        updateStatus(snark);
    8541110    }
    8551111   
     1112    /**
     1113     * A Snark.CompleteListener method.
     1114     */
    8561115    public void updateStatus(Snark snark) {
    857         saveTorrentStatus(snark.meta, snark.storage.getBitField(), snark.storage.getFilePriorities());
    858     }
    859    
     1116        MetaInfo meta = snark.getMetaInfo();
     1117        Storage storage = snark.getStorage();
     1118        if (meta != null && storage != null)
     1119            saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities());
     1120    }
     1121   
     1122    /**
     1123     * We transitioned from magnet mode, we have now initialized our
     1124     * metainfo and storage. The listener should now call getMetaInfo()
     1125     * and save the data to disk.
     1126     * A Snark.CompleteListener method.
     1127     *
     1128     * @return the new name for the torrent or null on error
     1129     * @since 0.8.4
     1130     */
     1131    public String gotMetaInfo(Snark snark) {
     1132        MetaInfo meta = snark.getMetaInfo();
     1133        Storage storage = snark.getStorage();
     1134        if (meta != null && storage != null) {
     1135            String rejectMessage = validateTorrent(meta);
     1136            if (rejectMessage != null) {
     1137                addMessage(rejectMessage);
     1138                snark.stopTorrent();
     1139                return null;
     1140            }
     1141            saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities
     1142            String name = (new File(getDataDir(), storage.getBaseName() + ".torrent")).getAbsolutePath();
     1143            try {
     1144                synchronized (_snarks) {
     1145                    locked_writeMetaInfo(meta, name);
     1146                    // put it in the list under the new name
     1147                    _snarks.remove(snark.getName());
     1148                    _snarks.put(name, snark);
     1149                }
     1150                _magnets.remove(snark.getName());
     1151                removeMagnetStatus(snark.getInfoHash());
     1152                addMessage(_("Metainfo received for {0}", snark.getName()));
     1153                addMessage(_("Starting up torrent {0}", storage.getBaseName()));
     1154                return name;
     1155            } catch (IOException ioe) {
     1156                addMessage(_("Failed to copy torrent file to {0}", name));
     1157                _log.error("Failed to write torrent file", ioe);
     1158            }
     1159        }
     1160        return null;
     1161    }
     1162
     1163    // End Snark.CompleteListeners
     1164
     1165    /**
     1166     * Add all magnets from the config file
     1167     * @since 0.8.4
     1168     */
     1169    private void addMagnets() {
     1170        for (Object o : _config.keySet()) {
     1171            String k = (String) o;
     1172            if (k.startsWith(PROP_META_MAGNET_PREFIX)) {
     1173                String b64 = k.substring(PROP_META_MAGNET_PREFIX.length());
     1174                b64 = b64.replace('$', '=');
     1175                byte[] ih = Base64.decode(b64);
     1176                // ignore value
     1177                if (ih != null && ih.length == 20)
     1178                    addMagnet("Magnet: " + I2PSnarkUtil.toHex(ih), ih, false);
     1179                // else remove from config?
     1180            }
     1181        }
     1182    }
     1183
    8601184    private void monitorTorrents(File dir) {
    8611185        String fileNames[] = dir.list(TorrentFilenameFilter.instance());
     
    8881212            }
    8891213        }
     1214        // Don't remove magnet torrents that don't have a torrent file yet
     1215        existingNames.removeAll(_magnets);
    8901216        // now lets see which ones have been removed...
    8911217        for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) {
     
    9411267    /** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
    9421268    public static final String PROP_TRACKERS = "i2psnark.trackers";
    943     private static Map trackerMap = null;
     1269    private static Map<String, String> trackerMap = null;
    9441270    /** sorted map of name to announceURL=baseURL */
    945     public Map getTrackers() {
     1271    public Map<String, String> getTrackers() {
    9461272        if (trackerMap != null) // only do this once, can't be updated while running
    9471273            return trackerMap;
    948         Map rv = new TreeMap();
     1274        Map<String, String> rv = new TreeMap();
    9491275        String trackers = _config.getProperty(PROP_TRACKERS);
    9501276        if ( (trackers == null) || (trackers.trim().length() <= 0) )
     
    9851311            for (Iterator iter = names.iterator(); iter.hasNext(); ) {
    9861312                Snark snark = getTorrent((String)iter.next());
    987                 if ( (snark != null) && (!snark.stopped) )
     1313                if ( (snark != null) && (!snark.isStopped()) )
    9881314                    snark.stopTorrent();
    9891315            }
     1316//save magnets
    9901317        }
    9911318    }
  • apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java

    r65c61864 rb55bfd03  
    3939
    4040    // And finally call the normal starting point.
    41     Snark.main(args);
     41    //Snark.main(args);
     42    System.err.println("unsupported");
    4243  }
    4344}
  • apps/i2psnark/java/src/org/klomp/snark/Storage.java

    r65c61864 rb55bfd03  
    8888   * with an appropriate MetaInfo file as can be announced on the
    8989   * given announce String location.
     90   *
     91   * @param announce may be null
     92   * @param listener may be null
    9093   */
    9194  public Storage(I2PSnarkUtil util, File baseFile, String announce, StorageListener listener)
     
    98101   
    99102    long total = 0;
    100     ArrayList lengthsList = new ArrayList();
     103    ArrayList<Long> lengthsList = new ArrayList();
    101104    for (int i = 0; i < lengths.length; i++)
    102105      {
    103106        long length = lengths[i];
    104107        total += length;
    105         lengthsList.add(new Long(length));
     108        lengthsList.add(Long.valueOf(length));
    106109      }
    107110
     
    120123    needed = 0;
    121124
    122     List files = new ArrayList();
     125    List<List<String>> files = new ArrayList();
    123126    for (int i = 0; i < names.length; i++)
    124127      {
    125         List file = new ArrayList();
     128        List<String> file = new ArrayList();
    126129        StringTokenizer st = new StringTokenizer(names[i], File.separator);
    127130        while (st.hasMoreTokens())
     
    536539      // the following sets the needed variable
    537540      changed = true;
    538       checkCreateFiles();
     541      checkCreateFiles(false);
    539542    }
    540543    if (complete()) {
     
    591594   * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
    592595   */
    593   private static String filterName(String name)
     596  public static String filterName(String name)
    594597  {
    595598    if (name.equals(".") || name.equals(" "))
     
    647650   * This is called at the beginning, and at presumed completion,
    648651   * so we have to be careful about locking.
    649    */
    650   private void checkCreateFiles() throws IOException
     652   *
     653   * @param recheck if true, this is a check after we downloaded the
     654   *        last piece, and we don't modify the global bitfield unless
     655   *        the check fails.
     656   */
     657  private void checkCreateFiles(boolean recheck) throws IOException
    651658  {
    652659    // Whether we are resuming or not,
     
    655662
    656663    _probablyComplete = true;
    657     needed = metainfo.getPieces();
     664    // use local variables during the check
     665    int need = metainfo.getPieces();
     666    BitField bfield;
     667    if (recheck) {
     668        bfield = new BitField(need);
     669    } else {
     670        bfield = bitfield;
     671    }
    658672
    659673    // Make sure all files are available and of correct length
     
    716730            if (correctHash)
    717731              {
    718                 bitfield.set(i);
    719                 needed--;
     732                bfield.set(i);
     733                need--;
    720734              }
    721735
     
    737751    //}
    738752
     753    // do this here so we don't confuse the user during checking
     754    needed = need;
     755    if (recheck && need > 0) {
     756        // whoops, recheck failed
     757        synchronized(bitfield) {
     758            bitfield = bfield;
     759        }
     760    }
     761
    739762    if (listener != null) {
    740763      listener.storageAllChecked(this);
     
    751774    // XXX - Is this the best way to make sure we have enough space for
    752775    // the whole file?
    753     listener.storageCreateFile(this, names[nr], lengths[nr]);
     776    if (listener != null)
     777        listener.storageCreateFile(this, names[nr], lengths[nr]);
    754778    final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
    755779    byte[] zeros;
     
    900924      // and also call listener.storageCompleted() if the double-check
    901925      // was successful.
    902       // Todo: set a listener variable so the web shows "checking" and don't
    903       // have the user panic when completed amount goes to zero temporarily?
    904       needed = metainfo.getPieces();
    905       bitfield = new BitField(needed);
    906       checkCreateFiles();
     926      checkCreateFiles(true);
    907927      if (needed > 0) {
    908928        if (listener != null)
  • apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java

    r65c61864 rb55bfd03  
    3535
    3636import net.i2p.I2PAppContext;
     37import net.i2p.data.Hash;
    3738import net.i2p.util.I2PAppThread;
    3839import net.i2p.util.Log;
     40
     41import org.klomp.snark.dht.DHT;
    3942
    4043/**
     
    6467  private final MetaInfo meta;
    6568  private final PeerCoordinator coordinator;
     69  private final Snark snark;
    6670  private final int port;
    6771
     
    7175  private List trackers;
    7276
    73   public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator)
     77  /**
     78   * @param meta null if in magnet mode
     79   */
     80  public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator, Snark snark)
    7481  {
    7582    super();
    7683    // Set unique name.
    77     String id = urlencode(coordinator.getID());
     84    String id = urlencode(snark.getID());
    7885    setName("TrackerClient " + id.substring(id.length() - 12));
    7986    _util = util;
    8087    this.meta = meta;
    8188    this.coordinator = coordinator;
     89    this.snark = snark;
    8290
    8391    this.port = 6881; //(port == -1) ? 9 : port;
     
    119127  public void run()
    120128  {
    121     String infoHash = urlencode(meta.getInfoHash());
    122     String peerID = urlencode(coordinator.getID());
    123 
    124     _log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash);
    125    
     129    String infoHash = urlencode(snark.getInfoHash());
     130    String peerID = urlencode(snark.getID());
     131
     132
    126133    // Construct the list of trackers for this torrent,
    127134    // starting with the primary one listed in the metainfo,
     
    131138    // todo: check for b32 matches as well
    132139    trackers = new ArrayList(2);
    133     String primary = meta.getAnnounce();
    134     if (isValidAnnounce(primary)) {
    135         trackers.add(new Tracker(meta.getAnnounce(), true));
    136     } else {
    137         _log.warn("Skipping invalid or non-i2p announce: " + primary);
    138     }
     140    String primary = null;
     141    if (meta != null) {
     142        primary = meta.getAnnounce();
     143        if (isValidAnnounce(primary)) {
     144            trackers.add(new Tracker(meta.getAnnounce(), true));
     145            _log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
     146        } else {
     147            _log.warn("Skipping invalid or non-i2p announce: " + primary);
     148        }
     149    }
     150    if (primary == null)
     151        primary = "";
    139152    List tlist = _util.getOpenTrackers();
    140153    if (tlist != null) {
     
    161174             if (primary.startsWith("http://i2p/" + dest))
    162175                continue;
    163              trackers.add(new Tracker(url, false));
     176             // opentrackers are primary if we don't have primary
     177             trackers.add(new Tracker(url, primary.equals("")));
    164178             _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
    165179        }
    166180    }
    167181
    168     if (tlist.isEmpty()) {
     182    if (trackers.isEmpty()) {
    169183        // FIXME really need to get this message to the gui
    170184        stop = true;
    171185        _log.error("No valid trackers for infoHash: " + infoHash);
     186        // FIXME keep going if DHT enabled
    172187        return;
    173188    }
     
    189204        while(!stop)
    190205          {
     206            // Local DHT tracker announce
     207            if (_util.getDHT() != null)
     208                _util.getDHT().announce(snark.getInfoHash());
    191209            try
    192210              {
     
    201219                } else if (completed && runStarted)
    202220                  delay = 3*SLEEP*60*1000 + random;
    203                 else if (coordinator.trackerProblems != null && ++consecutiveFails < MAX_CONSEC_FAILS)
     221                else if (snark.getTrackerProblems() != null && ++consecutiveFails < MAX_CONSEC_FAILS)
    204222                  delay = INITIAL_SLEEP;
    205223                else
     
    222240            uploaded = coordinator.getUploaded();
    223241            downloaded = coordinator.getDownloaded();
    224             left = coordinator.getLeft();
     242            left = coordinator.getLeft();   // -1 in magnet mode
    225243           
    226244            // First time we got a complete download?
     
    252270                                                 event);
    253271
    254                     coordinator.trackerProblems = null;
     272                    snark.setTrackerProblems(null);
    255273                    tr.trackerProblems = null;
    256274                    tr.registerFails = 0;
     
    261279                    tr.started = true;
    262280
    263                     Set peers = info.getPeers();
     281                    Set<Peer> peers = info.getPeers();
    264282                    tr.seenPeers = info.getPeerCount();
    265                     if (coordinator.trackerSeenPeers < tr.seenPeers) // update rising number quickly
    266                         coordinator.trackerSeenPeers = tr.seenPeers;
    267                     if ( (left > 0) && (!completed) ) {
     283                    if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
     284                        snark.setTrackerSeenPeers(tr.seenPeers);
     285
     286                    // pass everybody over to our tracker
     287                    if (_util.getDHT() != null) {
     288                        for (Peer peer : peers) {
     289                            _util.getDHT().announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
     290                        }
     291                    }
     292
     293                    if ( (left != 0) && (!completed) ) {
    268294                        // we only want to talk to new people if we need things
    269295                        // from them (duh)
    270                         List ordered = new ArrayList(peers);
     296                        List<Peer> ordered = new ArrayList(peers);
    271297                        Collections.shuffle(ordered, r);
    272                         Iterator it = ordered.iterator();
     298                        Iterator<Peer> it = ordered.iterator();
    273299                        while ((!stop) && it.hasNext()) {
    274                           Peer cur = (Peer)it.next();
     300                          Peer cur = it.next();
    275301                          // FIXME if id == us || dest == us continue;
    276302                          // only delay if we actually make an attempt to add peer
     
    294320                    // don't show secondary tracker problems to the user
    295321                    if (tr.isPrimary)
    296                       coordinator.trackerProblems = tr.trackerProblems;
     322                      snark.setTrackerProblems(tr.trackerProblems);
    297323                    if (tr.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
    298324                      // Give a guy some time to register it if using opentrackers too
    299325                      if (trackers.size() == 1) {
    300326                        stop = true;
    301                         coordinator.snark.stopTorrent();
     327                        snark.stopTorrent();
    302328                      } else { // hopefully each on the opentrackers list is really open
    303329                        if (tr.registerFails++ > MAX_REGISTER_FAILS)
     
    316342            }  // *** end of trackers loop here
    317343
     344            // Get peers from DHT
     345            // FIXME this needs to be in its own thread
     346            if (_util.getDHT() != null && !stop) {
     347                int numwant;
     348                if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
     349                    numwant = 1;
     350                else
     351                    numwant = _util.getMaxConnections();
     352                List<Hash> hashes = _util.getDHT().getPeers(snark.getInfoHash(), numwant, 2*60*1000);
     353                _util.debug("Got " + hashes + " from DHT", Snark.INFO);
     354                // announce  ourselves while the token is still good
     355                // FIXME this needs to be in its own thread
     356                if (!stop) {
     357                    int good = _util.getDHT().announce(snark.getInfoHash(), 8, 5*60*1000);
     358                    _util.debug("Sent " + good + " good announces to DHT", Snark.INFO);
     359                }
     360
     361                // now try these peers
     362                if ((!stop) && !hashes.isEmpty()) {
     363                    List<Peer> peers = new ArrayList(hashes.size());
     364                    for (Hash h : hashes) {
     365                        PeerID pID = new PeerID(h.getData());
     366                        peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
     367                    }
     368                    Collections.shuffle(peers, r);
     369                    Iterator<Peer> it = peers.iterator();
     370                    while ((!stop) && it.hasNext()) {
     371                        Peer cur = it.next();
     372                        if (coordinator.addPeer(cur)) {
     373                            int delay = DELAY_MUL;
     374                            delay *= r.nextInt(10);
     375                            delay += DELAY_MIN;
     376                            try { Thread.sleep(delay); } catch (InterruptedException ie) {}
     377                         }
     378                    }
     379                }
     380            }
     381
     382
    318383            // we could try and total the unique peers but that's too hard for now
    319             coordinator.trackerSeenPeers = maxSeenPeers;
     384            snark.setTrackerSeenPeers(maxSeenPeers);
    320385            if (!runStarted)
    321386                _util.debug("         Retrying in one minute...", Snark.DEBUG);
     
    330395    finally
    331396      {
     397        // Local DHT tracker unannounce
     398        if (_util.getDHT() != null)
     399            _util.getDHT().unannounce(snark.getInfoHash());
    332400        try
    333401          {
     
    352420    throws IOException
    353421  {
     422    // What do we send for left in magnet mode? Can we omit it?
     423    long tleft = left >= 0 ? left : 1;
    354424    String s = tr.announce
    355425      + "?info_hash=" + infoHash
     
    359429      + "&uploaded=" + uploaded
    360430      + "&downloaded=" + downloaded
    361       + "&left=" + left
     431      + "&left=" + tleft
    362432      + "&compact=1"   // NOTE: opentracker will return 400 for &compact alone
    363433      + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
    364     if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
     434    if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
    365435        s += "&numwant=0";
    366436    else
     
    378448        in = new FileInputStream(fetched);
    379449
    380         TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
    381                                            coordinator.getMetaInfo());
     450        TrackerInfo info = new TrackerInfo(in, snark.getID(),
     451                                           snark.getInfoHash(), snark.getMetaInfo());
    382452        _util.debug("TrackerClient response: " + info, Snark.INFO);
    383453
  • apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java

    r65c61864 rb55bfd03  
    4747  private int incomplete;
    4848
    49   public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
    50     throws IOException
    51   {
    52     this(new BDecoder(in), my_id, metainfo);
    53   }
    54 
    55   public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
    56     throws IOException
    57   {
    58     this(be.bdecodeMap().getMap(), my_id, metainfo);
    59   }
    60 
    61   public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
     49  /** @param metainfo may be null */
     50  public TrackerInfo(InputStream in, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     51    throws IOException
     52  {
     53    this(new BDecoder(in), my_id, infohash, metainfo);
     54  }
     55
     56  private TrackerInfo(BDecoder be, byte[] my_id, byte[] infohash, MetaInfo metainfo)
     57    throws IOException
     58  {
     59    this(be.bdecodeMap().getMap(), my_id, infohash, metainfo);
     60  }
     61
     62  private TrackerInfo(Map m, byte[] my_id, byte[] infohash, MetaInfo metainfo)
    6263    throws IOException
    6364  {
     
    8586            try {
    8687              // One big string (the official compact format)
    87               p = getPeers(bePeers.getBytes(), my_id, metainfo);
     88              p = getPeers(bePeers.getBytes(), my_id, infohash, metainfo);
    8889            } catch (InvalidBEncodingException ibe) {
    8990              // List of Dictionaries or List of Strings
    90               p = getPeers(bePeers.getList(), my_id, metainfo);
     91              p = getPeers(bePeers.getList(), my_id, infohash, metainfo);
    9192            }
    9293            peers = p;
     
    124125
    125126  /** List of Dictionaries or List of Strings */
    126   private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo)
     127  private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, byte[] infohash, MetaInfo metainfo)
    127128    throws IOException
    128129  {
     
    145146            }
    146147        }
    147         peers.add(new Peer(peerID, my_id, metainfo));
     148        peers.add(new Peer(peerID, my_id, infohash, metainfo));
    148149      }
    149150
     
    157158   *  @since 0.8.1
    158159   */
    159   private static Set<Peer> getPeers(byte[] l, byte[] my_id, MetaInfo metainfo)
     160  private static Set<Peer> getPeers(byte[] l, byte[] my_id, byte[] infohash, MetaInfo metainfo)
    160161    throws IOException
    161162  {
     
    173174            continue;
    174175        }
    175         peers.add(new Peer(peerID, my_id, metainfo));
     176        peers.add(new Peer(peerID, my_id, infohash, metainfo));
    176177      }
    177178
  • apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java

    r65c61864 rb55bfd03  
    2525import java.util.Map;
    2626
     27import net.i2p.data.Base64;
     28
    2729/**
    2830 * Holds different types that a bencoded byte array can represent.
     
    179181    if (value instanceof byte[])
    180182      {
     183        // try to do a nice job for debugging
    181184        byte[] bs = (byte[])value;
    182         // XXX - Stupid heuristic... and not UTF-8
    183         if (bs.length <= 12)
    184           valueString = new String(bs);
    185         else
    186           valueString = "bytes:" + bs.length;
     185        if (bs.length == 0)
     186          valueString =  "0 bytes";
     187        else if (bs.length <= 32) {
     188          StringBuilder buf = new StringBuilder(32);
     189          boolean bin = false;
     190          for (int i = 0; i < bs.length; i++) {
     191              int b = bs[i] & 0xff;
     192              // no UTF-8
     193              if (b < ' ' || b > 0x7e) {
     194                  bin = true;
     195                  break;
     196              }
     197          }
     198          if (bin && bs.length <= 8) {
     199              buf.append(bs.length).append(" bytes: 0x");
     200              for (int i = 0; i < bs.length; i++) {
     201                  int b = bs[i] & 0xff;
     202                  if (b < 16)
     203                      buf.append('0');
     204                  buf.append(Integer.toHexString(b));
     205              }
     206          } else if (bin) {
     207              buf.append(bs.length).append(" bytes: ").append(Base64.encode(bs));
     208          } else {
     209              buf.append('"').append(new String(bs)).append('"');
     210          }
     211          valueString = buf.toString();
     212        } else
     213          valueString =  bs.length + " bytes";
    187214      }
    188215    else
  • apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java

    r65c61864 rb55bfd03  
    5151    throws IOException, IllegalArgumentException
    5252  {
     53    if (o == null)
     54      throw new NullPointerException("Cannot bencode null");
    5355    if (o instanceof String)
    5456      bencode((String)o, out);
     
    6062      bencode((List)o, out);
    6163    else if (o instanceof Map)
    62       bencode((Map)o, out);
     64      bencode((Map<String, Object>)o, out);
    6365    else if (o instanceof BEValue)
    6466      bencode(((BEValue)o).getValue(), out);
     
    154156  }
    155157
    156   public static byte[] bencode(Map m)
     158  public static byte[] bencode(Map<String, Object> m)
    157159  {
    158160    try
     
    168170  }
    169171
    170   public static void bencode(Map m, OutputStream out) throws IOException
     172  public static void bencode(Map<String, Object> m, OutputStream out) throws IOException
    171173  {
    172174    out.write('d');
    173175
    174176    // Keys must be sorted. XXX - But is this the correct order?
    175     Set s = m.keySet();
    176     List l = new ArrayList(s);
     177    Set<String> s = m.keySet();
     178    List<String> l = new ArrayList(s);
    177179    Collections.sort(l);
    178180
    179     Iterator it = l.iterator();
     181    Iterator<String> it = l.iterator();
    180182    while(it.hasNext())
    181183      {
    182184        // Keys must be Strings.
    183         String key = (String)it.next();
     185        String key = it.next();
    184186        Object value = m.get(key);
    185187        bencode(key, out);
  • apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java

    r65c61864 rb55bfd03  
    77import java.io.PrintWriter;
    88import java.text.Collator;
     9import java.text.DecimalFormat;
    910import java.util.ArrayList;
    1011import java.util.Arrays;
     
    2728
    2829import net.i2p.I2PAppContext;
     30import net.i2p.data.Base32;
    2931import net.i2p.data.Base64;
    3032import net.i2p.data.DataHelper;
     
    3436import net.i2p.util.SecureFileOutputStream;
    3537
     38import org.klomp.snark.I2PSnarkUtil;
    3639import org.klomp.snark.MetaInfo;
    3740import org.klomp.snark.Peer;
     
    5962    private String _themePath;
    6063    private String _imgPath;
     64    private String _lastAnnounceURL = "";
    6165   
    6266    public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
     67    /** BEP 9 */
     68    private static final String MAGNET = "magnet:?xt=urn:btih:";
     69    /** http://sponge.i2p/files/maggotspec.txt */
     70    private static final String MAGGOT = "maggot://";
    6371 
    6472    @Override
     
    154162                resp.setContentType("text/html; charset=UTF-8");
    155163                Resource resource = getResource(pathInContext);
    156                 if (resource == null || (!resource.exists()) || !resource.isDirectory()) {
     164                if (resource == null || (!resource.exists())) {
    157165                    resp.sendError(HttpResponse.__404_Not_Found);
    158166                } else {
     
    377385            Snark snark = (Snark)snarks.get(i);
    378386            boolean showDebug = "2".equals(peerParam);
    379             boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam);
     387            boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.getInfoHash()).equals(peerParam);
    380388            displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug);
    381389        }
     
    479487                if (newURL.startsWith("http://")) {
    480488                    _manager.addMessage(_("Fetching {0}", urlify(newURL)));
    481                     I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
     489                    I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add", true);
    482490                    fetch.start();
     491                } else if (newURL.startsWith(MAGNET) || newURL.startsWith(MAGGOT)) {
     492                    addMagnet(newURL);
    483493                } else {
    484                     _manager.addMessage(_("Invalid URL - must start with http://"));
     494                    _manager.addMessage(_("Invalid URL: Must start with \"http://\", \"{0}\", or \"{1}\"", MAGNET, MAGGOT));
    485495                }
    486496            } else {
     
    495505                        String name = (String)iter.next();
    496506                        Snark snark = _manager.getTorrent(name);
    497                         if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
    498                             _manager.stopTorrent(name, false);
     507                        if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) {
     508                            _manager.stopTorrent(snark, false);
    499509                            break;
    500510                        }
     
    509519                    for (String name : _manager.listTorrentFiles()) {
    510520                        Snark snark = _manager.getTorrent(name);
    511                         if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
     521                        if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) {
    512522                            snark.startTorrent();
    513                             if (snark.storage != null)
    514                                 name = snark.storage.getBaseName();
    515                             _manager.addMessage(_("Starting up torrent {0}", name));
     523                            _manager.addMessage(_("Starting up torrent {0}", snark.getBaseName()));
    516524                            break;
    517525                        }
     
    527535                        String name = (String)iter.next();
    528536                        Snark snark = _manager.getTorrent(name);
    529                         if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
    530                             _manager.stopTorrent(name, true);
     537                        if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) {
     538                            MetaInfo meta = snark.getMetaInfo();
     539                            if (meta == null) {
     540                                // magnet - remove and delete are the same thing
     541                                _manager.deleteMagnet(snark);
     542                                _manager.addMessage(_("Magnet deleted: {0}", name));
     543                                return;
     544                            }
     545                            _manager.stopTorrent(snark, true);
    531546                            // should we delete the torrent file?
    532547                            // yeah, need to, otherwise it'll get autoadded again (at the moment
     
    547562                        String name = (String)iter.next();
    548563                        Snark snark = _manager.getTorrent(name);
    549                         if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
    550                             _manager.stopTorrent(name, true);
     564                        if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) {
     565                            MetaInfo meta = snark.getMetaInfo();
     566                            if (meta == null) {
     567                                // magnet - remove and delete are the same thing
     568                                _manager.deleteMagnet(snark);
     569                                _manager.addMessage(_("Magnet deleted: {0}", name));
     570                                return;
     571                            }
     572                            _manager.stopTorrent(snark, true);
    551573                            File f = new File(name);
    552574                            f.delete();
    553575                            _manager.addMessage(_("Torrent file deleted: {0}", f.getAbsolutePath()));
    554                             List files = snark.meta.getFiles();
    555                             String dataFile = snark.meta.getName();
     576                            List files = meta.getFiles();
     577                            String dataFile = snark.getBaseName();
    556578                            f = new File(_manager.getDataDir(), dataFile);
    557579                            if (files == null) { // single file torrent
     
    613635                    _manager.addMessage(_("Error creating torrent - you must select a tracker"));
    614636                else if (baseFile.exists()) {
     637                    _lastAnnounceURL = announceURL;
     638                    if (announceURL.equals("none"))
     639                        announceURL = null;
    615640                    try {
     641                        // This may take a long time to check the storage, but since it already exists,
     642                        // it shouldn't be THAT bad, so keep it in this thread.
    616643                        Storage s = new Storage(_manager.util(), baseFile, announceURL, null);
    617644                        s.create();
    618645                        s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
    619646                        MetaInfo info = s.getMetaInfo();
    620                         File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
    621                         if (torrentFile.exists())
    622                             throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
    623                         _manager.saveTorrentStatus(info, s.getBitField(), null); // so addTorrent won't recheck
    624                         // DirMonitor could grab this first, maybe hold _snarks lock?
    625                         FileOutputStream out = new FileOutputStream(torrentFile);
    626                         out.write(info.getTorrentData());
    627                         out.close();
     647                        File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent");
     648                        // FIXME is the storage going to stay around thanks to the info reference?
     649                        // now add it, but don't automatically start it
     650                        _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), true);
    628651                        _manager.addMessage(_("Torrent created for \"{0}\"", baseFile.getName()) + ": " + torrentFile.getAbsolutePath());
    629                         // now fire it up, but don't automatically seed it
    630                         _manager.addTorrent(torrentFile.getCanonicalPath(), true);
    631                         _manager.addMessage(_("Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\"", baseFile.getName()));
     652                        if (announceURL != null)
     653                            _manager.addMessage(_("Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\"", baseFile.getName()));
    632654                    } catch (IOException ioe) {
    633655                        _manager.addMessage(_("Error creating a torrent for \"{0}\"", baseFile.getAbsolutePath()) + ": " + ioe.getMessage());
     
    644666            for (int i = 0; i < snarks.size(); i++) {
    645667                Snark snark = (Snark)snarks.get(i);
    646                 if (!snark.stopped)
    647                     _manager.stopTorrent(snark.torrent, false);
     668                if (!snark.isStopped())
     669                    _manager.stopTorrent(snark, false);
    648670            }
    649671            if (_manager.util().connected()) {
     
    658680            for (int i = 0; i < snarks.size(); i++) {
    659681                Snark snark = (Snark)snarks.get(i);
    660                 if (snark.stopped)
     682                if (snark.isStopped())
    661683                    snark.startTorrent();
    662684            }
     
    726748    private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers,
    727749                              boolean isDegraded, boolean noThinsp, boolean showDebug) throws IOException {
    728         String filename = snark.torrent;
     750        String filename = snark.getName();
    729751        File f = new File(filename);
    730752        filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
     
    734756        String fullFilename = filename;
    735757        if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH) {
    736             fullFilename = new String(filename);
    737             filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "&hellip;";
    738         }
    739         long total = snark.meta.getTotalLength();
     758            String start = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH);
     759            if (start.indexOf(" ") < 0 && start.indexOf("-") < 0) {
     760                // browser has nowhere to break it
     761                fullFilename = filename;
     762                filename = start + "&hellip;";
     763            }
     764        }
     765        long total = snark.getTotalLength();
    740766        // Early typecast, avoid possibly overflowing a temp integer
    741         long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
     767        long remaining = (long) snark.getNeeded() * (long) snark.getPieceLength(0);
    742768        if (remaining > total)
    743769            remaining = total;
    744         long downBps = 0;
    745         long upBps = 0;
    746         if (snark.coordinator != null) {
    747             downBps = snark.coordinator.getDownloadRate();
    748             upBps = snark.coordinator.getUploadRate();
    749         }
     770        long downBps = snark.getDownloadRate();
     771        long upBps = snark.getUploadRate();
    750772        long remainingSeconds;
    751773        if (downBps > 0)
     
    753775        else
    754776            remainingSeconds = -1;
    755         boolean isRunning = !snark.stopped;
    756         long uploaded = 0;
    757         if (snark.coordinator != null) {
    758             uploaded = snark.coordinator.getUploaded();
    759             stats[0] += snark.coordinator.getDownloaded();
    760         }
     777        boolean isRunning = !snark.isStopped();
     778        long uploaded = snark.getUploaded();
     779        stats[0] += snark.getDownloaded();
    761780        stats[1] += uploaded;
    762781        if (isRunning) {
     
    766785        stats[5] += total;
    767786       
    768         boolean isValid = snark.meta != null;
    769         boolean singleFile = snark.meta.getFiles() == null;
     787        MetaInfo meta = snark.getMetaInfo();
     788        // isValid means isNotMagnet
     789        boolean isValid = meta != null;
     790        boolean isMultiFile = isValid && meta.getFiles() != null;
    770791       
    771         String err = null;
    772         int curPeers = 0;
    773         int knownPeers = 0;
    774         if (snark.coordinator != null) {
    775             err = snark.coordinator.trackerProblems;
    776             curPeers = snark.coordinator.getPeerCount();
    777             stats[4] += curPeers;
    778             knownPeers = Math.max(curPeers, snark.coordinator.trackerSeenPeers);
    779         }
     792        String err = snark.getTrackerProblems();
     793        int curPeers = snark.getPeerCount();
     794        stats[4] += curPeers;
     795        int knownPeers = Math.max(curPeers, snark.getTrackerSeenPeers());
    780796       
    781797        String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
     
    784800            if (isRunning && curPeers > 0 && !showPeers)
    785801                statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") +
    786                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
     802                               ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" +
    787803                               curPeers + thinsp(noThinsp) +
    788804                               ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
     
    797813                "<br>" + err;
    798814            }
    799         } else if (remaining <= 0) {
     815        } else if (remaining == 0) {  // < 0 means no meta size yet
    800816            if (isRunning && curPeers > 0 && !showPeers)
    801817                statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") +
    802                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
     818                               ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" +
    803819                               curPeers + thinsp(noThinsp) +
    804820                               ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
     
    812828            if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
    813829                statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("OK") +
    814                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
     830                               ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" +
    815831                               curPeers + thinsp(noThinsp) +
    816832                               ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
     
    821837            else if (isRunning && curPeers > 0 && !showPeers)
    822838                statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Stalled") +
    823                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
     839                               ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" +
    824840                               curPeers + thinsp(noThinsp) +
    825841                               ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
     
    842858
    843859        out.write("<td class=\"" + rowClass + "\">");
    844         // temporarily hardcoded for postman* and anonymity, requires bytemonsoon patch for lookup by info_hash
    845         String announce = snark.meta.getAnnounce();
    846         if (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr") ||
    847             announce.startsWith("http://lnQ6yoBT") || announce.startsWith("http://tracker2.postman.i2p/") || announce.startsWith("http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/")) {
    848             Map trackers = _manager.getTrackers();
    849             for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) {
    850                 Map.Entry entry = (Map.Entry)iter.next();
    851                 String name = (String)entry.getKey();
    852                 String baseURL = (String)entry.getValue();
    853                 if (!(baseURL.startsWith(announce) || // vvv hack for non-b64 announce in list vvv
    854                       (announce.startsWith("http://lnQ6yoBT") && baseURL.startsWith("http://tracker2.postman.i2p/")) ||
    855                       (announce.startsWith("http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/") && baseURL.startsWith("http://tracker2.postman.i2p/"))))
    856                     continue;
    857                 int e = baseURL.indexOf('=');
    858                 if (e < 0)
    859                     continue;
    860                 baseURL = baseURL.substring(e + 1);
    861                 out.write("<a href=\"" + baseURL + "details.php?dllist=1&amp;filelist=1&amp;info_hash=");
    862                 out.write(TrackerClient.urlencode(snark.meta.getInfoHash()));
    863                 out.write("\" title=\"" + _("Details at {0} tracker", name) + "\" target=\"_blank\">");
    864                 out.write("<img alt=\"" + _("Info") + "\" border=\"0\" src=\"" + _imgPath + "details.png\">");
    865                 out.write("</a>");
    866                 break;
    867             }
     860        if (isValid) {
     861            StringBuilder buf = new StringBuilder(128);
     862            buf.append("<a href=\"").append(snark.getBaseName())
     863               .append("/\" title=\"").append(_("Torrent details"))
     864               .append("\"><img alt=\"").append(_("Info")).append("\" border=\"0\" src=\"")
     865               .append(_imgPath).append("details.png\"></a>");
     866             out.write(buf.toString());
    868867        }
    869868
    870869        out.write("</td>\n<td class=\"" + rowClass + "\">");
    871870        StringBuilder buf = null;
    872         if (remaining == 0 || snark.meta.getFiles() != null) {
     871        if (remaining == 0 || isMultiFile) {
    873872            buf = new StringBuilder(128);
    874             buf.append("<a href=\"").append(snark.storage.getBaseName());
    875             if (snark.meta.getFiles() != null)
     873            buf.append("<a href=\"").append(snark.getBaseName());
     874            if (isMultiFile)
    876875                buf.append('/');
    877876            buf.append("\" title=\"");
    878             if (snark.meta.getFiles() != null)
     877            if (isMultiFile)
    879878                buf.append(_("View files"));
    880879            else
     
    884883        }
    885884        String icon;
    886         if (snark.meta.getFiles() != null)
     885        if (isMultiFile)
    887886            icon = "folder";
     887        else if (isValid)
     888            icon = toIcon(meta.getName());
    888889        else
    889             icon = toIcon(snark.meta.getName());
    890         if (remaining == 0 || snark.meta.getFiles() != null) {
     890            icon = "magnet";
     891        if (remaining == 0 || isMultiFile) {
    891892            out.write(toImg(icon, _("Open")));
    892893            out.write("</a>");
     
    895896        }
    896897        out.write("</td><td class=\"snarkTorrentName " + rowClass + "\">");
    897         if (remaining == 0 || snark.meta.getFiles() != null)
     898        if (remaining == 0 || isMultiFile)
    898899            out.write(buf.toString());
    899900        out.write(filename);
    900         if (remaining == 0 || snark.meta.getFiles() != null)
     901        if (remaining == 0 || isMultiFile)
    901902            out.write("</a>");
    902903
     
    908909        if (remaining > 0)
    909910            out.write(formatSize(total-remaining) + thinsp(noThinsp) + formatSize(total));
     911        else if (remaining == 0)
     912            out.write(formatSize(total)); // 3GB
    910913        else
    911             out.write(formatSize(total)); // 3GB
     914            out.write("??");  // no meta size yet
    912915        out.write("</td>\n\t");
    913916        out.write("<td align=\"right\" class=\"snarkTorrentUploaded " + rowClass + "\">");
    914         if(isRunning)
     917        if(isRunning && isValid)
    915918           out.write(formatSize(uploaded));
    916919        out.write("</td>\n\t");
    917920        out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">");
    918         if(isRunning && remaining > 0)
     921        if(isRunning && remaining != 0)
    919922            out.write(formatSize(downBps) + "ps");
    920923        out.write("</td>\n\t");
    921924        out.write("<td align=\"right\" class=\"snarkTorrentRateUp\">");
    922         if(isRunning)
     925        if(isRunning && isValid)
    923926            out.write(formatSize(upBps) + "ps");
    924927        out.write("</td>\n\t");
    925928        out.write("<td align=\"center\" class=\"snarkTorrentAction " + rowClass + "\">");
    926         String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash());
    927         String b64 = Base64.encode(snark.meta.getInfoHash());
     929        String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.getInfoHash());
     930        String b64 = Base64.encode(snark.getInfoHash());
    928931        if (showPeers)
    929932            parameters = parameters + "&p=1";
     
    940943                out.write("</a>");
    941944        } else {
    942             if (isValid) {
    943945                if (isDegraded)
    944946                    out.write("<a href=\"/i2psnark/?action=Start_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
     
    951953                if (isDegraded)
    952954                    out.write("</a>");
    953             }
    954 
    955             if (isDegraded)
    956                 out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
    957             else
    958                 out.write("<input type=\"image\" name=\"action_Remove_" + b64 + "\" value=\"foo\" title=\"");
    959             out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
    960             out.write("\" onclick=\"if (!confirm('");
    961             // Can't figure out how to escape double quotes inside the onclick string.
    962             // Single quotes in translate strings with parameters must be doubled.
    963             // Then the remaining single quite must be escaped
    964             out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename));
    965             out.write("')) { return false; }\"");
    966             out.write(" src=\"" + _imgPath + "remove.png\" alt=\"");
    967             out.write(_("Remove"));
    968             out.write("\">");
    969             if (isDegraded)
    970                 out.write("</a>");
     955
     956            if (isValid) {
     957                if (isDegraded)
     958                    out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
     959                else
     960                    out.write("<input type=\"image\" name=\"action\" value=\"Remove_" + b64 + "\" title=\"");
     961                out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
     962                out.write("\" onclick=\"if (!confirm('");
     963                // Can't figure out how to escape double quotes inside the onclick string.
     964                // Single quotes in translate strings with parameters must be doubled.
     965                // Then the remaining single quite must be escaped
     966                out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename));
     967                out.write("')) { return false; }\"");
     968                out.write(" src=\"" + _imgPath + "remove.png\" alt=\"");
     969                out.write(_("Remove"));
     970                out.write("\">");
     971                if (isDegraded)
     972                    out.write("</a>");
     973            }
    971974
    972975            if (isDegraded)
     
    990993
    991994        if(showPeers && isRunning && curPeers > 0) {
    992             List<Peer> peers = snark.coordinator.peerList();
     995            List<Peer> peers = snark.getPeerList();
    993996            if (!showDebug)
    994997                Collections.sort(peers, new PeerComparator());
     
    10231026                out.write("</td>\n\t");
    10241027                out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
    1025                 float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces());
    1026                 if (pct == 100.0)
    1027                     out.write(_("Seed"));
    1028                 else {
    1029                     String ps = String.valueOf(pct);
    1030                     if (ps.length() > 5)
    1031                         ps = ps.substring(0, 5);
    1032                     out.write(ps + "%");
     1028                float pct;
     1029                if (isValid) {
     1030                    pct = (float) (100.0 * (float) peer.completed() / meta.getPieces());
     1031                    if (pct == 100.0)
     1032                        out.write(_("Seed"));
     1033                    else {
     1034                        String ps = String.valueOf(pct);
     1035                        if (ps.length() > 5)
     1036                            ps = ps.substring(0, 5);
     1037                        out.write(ps + "%");
     1038                    }
     1039                } else {
     1040                    pct = (float) 101.0;
     1041                    // until we get the metainfo we don't know how many pieces there are
     1042                    //out.write("??");
    10331043                }
    10341044                out.write("</td>\n\t");
     
    10521062                out.write("</td>\n\t");
    10531063                out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
    1054                 if (pct != 100.0) {
     1064                if (isValid && pct < 100.0) {
    10551065                    if (peer.isInterested() && !peer.isChoking()) {
    10561066                        out.write("<span class=\"unchoked\">");
     
    10951105    }
    10961106
     1107    /**
     1108     *  @return string or null
     1109     *  @since 0.8.4
     1110     */
     1111    private String getTrackerLink(String announce, byte[] infohash) {
     1112        // temporarily hardcoded for postman* and anonymity, requires bytemonsoon patch for lookup by info_hash
     1113        if (announce != null && (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr") ||
     1114              announce.startsWith("http://lnQ6yoBT") || announce.startsWith("http://tracker2.postman.i2p/") ||
     1115              announce.startsWith("http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/"))) {
     1116            Map<String, String> trackers = _manager.getTrackers();
     1117            for (Map.Entry<String, String> entry : trackers.entrySet()) {
     1118                String baseURL = entry.getValue();
     1119                if (!(baseURL.startsWith(announce) || // vvv hack for non-b64 announce in list vvv
     1120                      (announce.startsWith("http://lnQ6yoBT") && baseURL.startsWith("http://tracker2.postman.i2p/")) ||
     1121                      (announce.startsWith("http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/") && baseURL.startsWith("http://tracker2.postman.i2p/"))))
     1122                    continue;
     1123                int e = baseURL.indexOf('=');
     1124                if (e < 0)
     1125                    continue;
     1126                baseURL = baseURL.substring(e + 1);
     1127                String name = entry.getKey();
     1128                StringBuilder buf = new StringBuilder(128);
     1129                buf.append("<a href=\"").append(baseURL).append("details.php?dllist=1&amp;filelist=1&amp;info_hash=")
     1130                   .append(TrackerClient.urlencode(infohash))
     1131                   .append("\" title=\"").append(_("Details at {0} tracker", name)).append("\" target=\"_blank\">" +
     1132                          "<img alt=\"").append(_("Info")).append("\" border=\"0\" src=\"")
     1133                   .append(_imgPath).append("details.png\"></a>");
     1134                return buf.toString();
     1135            }
     1136        }
     1137        return null;
     1138    }
     1139
    10971140    private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
    1098         String uri = req.getRequestURI();
    10991141        String newURL = req.getParameter("newURL");
    11001142        if ( (newURL == null) || (newURL.trim().length() <= 0) )
     
    11371179   
    11381180    private void writeSeedForm(PrintWriter out, HttpServletRequest req) throws IOException {
    1139         String uri = req.getRequestURI();
    11401181        String baseFile = req.getParameter("baseFile");
    11411182        if (baseFile == null || baseFile.trim().length() <= 0)
     
    11681209        out.write(_("Select a tracker"));
    11691210        out.write("</option>\n");
     1211        // todo remember this one with _lastAnnounceURL also
     1212        out.write("<option value=\"none\">");
     1213        out.write(_("Open trackers and DHT only"));
     1214        out.write("</option>\n");
    11701215        Map trackers = _manager.getTrackers();
    11711216        for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) {
     
    11761221            if (e > 0)
    11771222                announceURL = announceURL.substring(0, e);
     1223            if (announceURL.equals(_lastAnnounceURL))
     1224                announceURL += "\" selected=\"selected";
    11781225            out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n");
    11791226        }
     
    11911238   
    11921239    private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
    1193         String uri = req.getRequestURI();
    11941240        String dataDir = _manager.getDataDir().getAbsolutePath();
    11951241        boolean autoStart = _manager.shouldAutoStart();
     
    13091355        out.write(renderOptions(0, 4, options.remove("outbound.length"), "outbound.length", HOP));
    13101356
    1311         out.write("<tr><td>");
    1312         out.write(_("I2CP host"));
    1313         out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\""
    1314                   + _manager.util().getI2CPHost() + "\" size=\"15\" > ");
    1315 
    1316         out.write("<tr><td>");
    1317         out.write(_("I2CP port"));
    1318         out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
    1319                   + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
     1357        if (!_context.isRouterContext()) {
     1358            out.write("<tr><td>");
     1359            out.write(_("I2CP host"));
     1360            out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\""
     1361                      + _manager.util().getI2CPHost() + "\" size=\"15\" > ");
     1362
     1363            out.write("<tr><td>");
     1364            out.write(_("I2CP port"));
     1365            out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
     1366                      + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
     1367        }
    13201368
    13211369        StringBuilder opts = new StringBuilder(64);
     
    13431391        out.write(_("Configuration"));
    13441392        out.write("</a></span></span></div>\n");
     1393    }
     1394
     1395    /**
     1396     *  @param url in base32 or hex, xt must be first magnet param
     1397     *  @since 0.8.4
     1398     */
     1399    private void addMagnet(String url) {
     1400        String ihash;
     1401        String name;
     1402        if (url.startsWith(MAGNET)) {
     1403            ihash = url.substring(MAGNET.length()).trim();
     1404            int amp = ihash.indexOf('&');
     1405            if (amp >= 0)
     1406                ihash = url.substring(0, amp);
     1407            name = "Magnet " + ihash;
     1408        } else if (url.startsWith(MAGGOT)) {
     1409            ihash = url.substring(MAGGOT.length()).trim();
     1410            int col = ihash.indexOf(':');
     1411            if (col >= 0)
     1412                ihash = url.substring(0, col);
     1413            name = "Maggot " + ihash;
     1414        } else {
     1415            return;
     1416        }
     1417        byte[] ih = null;
     1418        if (ihash.length() == 32) {
     1419            ih = Base32.decode(ihash);
     1420        } else if (ihash.length() == 40) {
     1421            //  Like DataHelper.fromHexString() but ensures no loss of leading zero bytes
     1422            ih = new byte[20];
     1423            try {
     1424                for (int i = 0; i < 20; i++) {
     1425                    ih[i] = (byte) (Integer.parseInt(ihash.substring(i*2, (i*2) + 2), 16) & 0xff);
     1426                }
     1427            } catch (NumberFormatException nfe) {
     1428                ih = null;
     1429            }
     1430        }
     1431        if (ih == null || ih.length != 20) {
     1432            _manager.addMessage(_("Invalid info hash in magnet URL {0}", url));
     1433            return;
     1434        }
     1435        _manager.addMagnet(name, ih, true);
    13451436    }
    13461437
     
    13831474    private String _(String s, Object o) {
    13841475        return _manager.util().getString(s, o);
     1476    }
     1477
     1478    /** translate */
     1479    private String _(String s, Object o, Object o2) {
     1480        return _manager.util().getString(s, o, o2);
    13851481    }
    13861482
     
    14601556        throws IOException
    14611557    {
    1462         if (!r.isDirectory())
    1463             return null;
    1464        
    1465         String[] ls = r.list();
    1466         if (ls==null)
    1467             return null;
    1468         Arrays.sort(ls, Collator.getInstance());
     1558        String[] ls = null;
     1559        if (r.isDirectory()) {
     1560            ls = r.list();
     1561            Arrays.sort(ls, Collator.getInstance());
     1562        }  // if r is not a directory, we are only showing torrent info section
    14691563       
    14701564        StringBuilder buf=new StringBuilder(4096);
     
    14881582        if (title.endsWith("/"))
    14891583            title = title.substring(0, title.length() - 1);
     1584        String directory = title;
    14901585        title = _("Torrent") + ": " + title;
    14911586        buf.append(title);
     
    14961591        if (parent)  // always true
    14971592            buf.append("<div class=\"page\"><div class=\"mainsection\">");
    1498         boolean showPriority = snark != null && !snark.storage.complete();
     1593        boolean showPriority = ls != null && snark != null && snark.getStorage() != null && !snark.getStorage().complete();
    14991594        if (showPriority)
    15001595            buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
    1501         buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" >" +
    1502             "<thead><tr><th>")
     1596        buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" ><thead>");
     1597        if (snark != null) {
     1598            // first row - torrent info
     1599            // FIXME center
     1600            buf.append("<tr><th colspan=\"" + (showPriority ? '4' : '3') + "\"><div>")
     1601                .append(_("Torrent")).append(": ").append(snark.getBaseName());
     1602            int pieces = snark.getPieces();
     1603            double completion = (pieces - snark.getNeeded()) / (double) pieces;
     1604            if (completion < 1.0)
     1605                buf.append("<br>").append(_("Completion")).append(": ").append((new DecimalFormat("0.00%")).format(completion));
     1606            else
     1607                buf.append("<br>").append(_("Complete"));
     1608            // else unknown
     1609            buf.append("<br>").append(_("Size")).append(": ").append(formatSize(snark.getTotalLength()));
     1610            MetaInfo meta = snark.getMetaInfo();
     1611            if (meta != null) {
     1612                List files = meta.getFiles();
     1613                int fileCount = files != null ? files.size() : 1;
     1614                buf.append("<br>").append(_("Files")).append(": ").append(fileCount);
     1615            }
     1616            buf.append("<br>").append(_("Pieces")).append(": ").append(pieces);
     1617            buf.append("<br>").append(_("Piece size")).append(": ").append(formatSize(snark.getPieceLength(0)));
     1618
     1619            if (meta != null) {
     1620                String announce = meta.getAnnounce();
     1621                if (announce != null) {
     1622                    buf.append("<br>");
     1623                    String trackerLink = getTrackerLink(announce, snark.getInfoHash());
     1624                    if (trackerLink != null)
     1625                        buf.append(trackerLink).append(' ');
     1626                    buf.append(_("Tracker")).append(": ");
     1627                    if (announce.startsWith("http://"))
     1628                        announce = announce.substring(7);
     1629                    int slsh = announce.indexOf('/');
     1630                    if (slsh > 0)
     1631                        announce = announce.substring(0, slsh);
     1632                    buf.append(announce);
     1633                }
     1634            }
     1635
     1636            String hex = I2PSnarkUtil.toHex(snark.getInfoHash());
     1637            buf.append("<br>").append(toImg("magnet", _("Magnet link"))).append(" <a href=\"")
     1638               .append(MAGNET).append(hex).append("\">")
     1639               .append(MAGNET).append(hex).append("</a>");
     1640            // We don't have the hash of the torrent file
     1641            //buf.append("<br>").append(_("Maggot link")).append(": <a href=\"").append(MAGGOT).append(hex).append(':').append(hex).append("\">")
     1642            //   .append(MAGGOT).append(hex).append(':').append(hex).append("</a>");
     1643            buf.append("</div></th></tr>");
     1644        }
     1645        if (ls == null) {
     1646            // We are only showing the torrent info section
     1647            buf.append("</thead></table></div></div></BODY></HTML>");
     1648            return buf.toString();
     1649        }
     1650
     1651        // second row - dir info
     1652        buf.append("<tr><th>")
    15031653            .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;")
    1504             .append(title).append("</th><th align=\"right\">")
     1654            .append(_("Directory")).append(": ").append(directory).append("</th><th align=\"right\">")
    15051655            .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" >&nbsp;")
    15061656            .append(_("Size"));
     
    15431693                status = toImg("tick") + ' ' + _("Directory");
    15441694            } else {
    1545                 if (snark == null) {
     1695                if (snark == null || snark.getStorage() == null) {
    15461696                    // Assume complete, perhaps he removed a completed torrent but kept a bookmark
    15471697                    complete = true;
    15481698                    status = toImg("cancel") + ' ' + _("Torrent not found?");
    15491699                } else {
     1700                    Storage storage = snark.getStorage();
    15501701                    try {
    15511702                        File f = item.getFile();
    15521703                        if (f != null) {
    1553                             long remaining = snark.storage.remaining(f.getCanonicalPath());
     1704                            long remaining = storage.remaining(f.getCanonicalPath());
    15541705                            if (remaining < 0) {
    15551706                                complete = true;
     
    15591710                                status = toImg("tick") + ' ' + _("Complete");
    15601711                            } else {
    1561                                 int priority = snark.storage.getPriority(f.getCanonicalPath());
     1712                                int priority = storage.getPriority(f.getCanonicalPath());
    15621713                                if (priority < 0)
    15631714                                    status = toImg("cancel");
     
    16151766                File f = item.getFile();
    16161767                if ((!complete) && (!item.isDirectory()) && f != null) {
    1617                     int pri = snark.storage.getPriority(f.getCanonicalPath());
     1768                    int pri = snark.getStorage().getPriority(f.getCanonicalPath());
    16181769                    buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    16191770                    if (pri > 0)
     
    17171868    /** @since 0.8.1 */
    17181869    private void savePriorities(Snark snark, Map postParams) {
     1870        Storage storage = snark.getStorage();
     1871        if (storage == null)
     1872            return;
    17191873        Set<Map.Entry> entries = postParams.entrySet();
    17201874        for (Map.Entry entry : entries) {
     
    17251879                    String val = ((String[])entry.getValue())[0];   // jetty arrays
    17261880                    int pri = Integer.parseInt(val);
    1727                     snark.storage.setPriority(file, pri);
     1881                    storage.setPriority(file, pri);
    17281882                    //System.err.println("Priority now " + pri + " for " + file);
    17291883                } catch (Throwable t) { t.printStackTrace(); }
    17301884            }
    17311885        }
    1732         if (snark.coordinator != null)
    1733             snark.coordinator.updatePiecePriorities();
    1734         _manager.saveTorrentStatus(snark.storage.getMetaInfo(), snark.storage.getBitField(), snark.storage.getFilePriorities());
     1886         snark.updatePiecePriorities();
     1887        _manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities());
    17351888    }
    17361889
     
    17541907                try {
    17551908                    in = new FileInputStream(file);
     1909                    // we do not retain this MetaInfo object, hopefully it will go away quickly
    17561910                    MetaInfo info = new MetaInfo(in);
     1911                    try { in.close(); } catch (IOException ioe) {}
     1912                    Snark snark = _manager.getTorrentByInfoHash(info.getInfoHash());
     1913                    if (snark != null) {
     1914                        _manager.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
     1915                        return;
     1916                    }
     1917
    17571918                    String name = info.getName();
    1758                     name = DataHelper.stripHTML(name);  // XSS
    1759                     name = name.replace('/', '_');
    1760                     name = name.replace('\\', '_');
    1761                     name = name.replace('&', '+');
    1762                     name = name.replace('\'', '_');
    1763                     name = name.replace('"', '_');
    1764                     name = name.replace('`', '_');
     1919                    name = Storage.filterName(name);
    17651920                    name = name + ".torrent";
    17661921                    File torrentFile = new File(_manager.getDataDir(), name);
     
    17741929                            _manager.addMessage(_("Torrent already in the queue: {0}", name));
    17751930                    } else {
    1776                         boolean success = FileUtil.copy(file.getAbsolutePath(), canonical, false);
    1777                         if (success) {
    1778                             SecureFileOutputStream.setPerms(torrentFile);
    1779                             _manager.addTorrent(canonical);
    1780                         } else {
    1781                             _manager.addMessage(_("Failed to copy torrent file to {0}", canonical));
    1782                         }
     1931                        // This may take a LONG time to create the storage.
     1932                        _manager.copyAndAddTorrent(file, canonical);
    17831933                    }
    17841934                } catch (IOException ioe) {
    17851935                    _manager.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
     1936                } catch (OutOfMemoryError oom) {
     1937                    _manager.addMessage(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + oom.getMessage());
    17861938                } finally {
    1787                     try { in.close(); } catch (IOException ioe) {}
     1939                    try { if (in != null) in.close(); } catch (IOException ioe) {}
    17881940                }
    17891941            } else {
Note: See TracChangeset for help on using the changeset viewer.