Changeset 9afff4f


Ignore:
Timestamp:
Oct 15, 2010 1:48:36 PM (9 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
24dd783
Parents:
1aba324
Message:
  • i2psnark: Add file priority feature; Use context random for shuffle; other cleanups
Location:
apps/i2psnark/java/src/org/klomp/snark
Files:
6 edited

Legend:

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

    r1aba324 r9afff4f  
    164164            if (opts.getProperty("outbound.nickname") == null)
    165165                opts.setProperty("outbound.nickname", "I2PSnark");
     166            // Dont do this for now, it is set in I2PSocketEepGet for announces,
     167            // we don't need fast handshake for peer connections.
     168            //if (opts.getProperty("i2p.streaming.connectDelay") == null)
     169            //    opts.setProperty("i2p.streaming.connectDelay", "500");
    166170            if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
    167171                opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
  • apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java

    r1aba324 r9afff4f  
    7979  private final CoordinatorListener listener;
    8080  public I2PSnarkUtil _util;
     81  private static final Random _random = I2PAppContext.getGlobalContext().random();
    8182 
    8283  public String trackerProblems = null;
     
    9899    // Randomize the first start time so multiple tasks are spread out,
    99100    // this will help the behavior with global limits
    100     Random r = I2PAppContext.getGlobalContext().random();
    101     timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
     101    timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + _random.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
    102102  }
    103103 
     
    108108    wantedPieces = new ArrayList();
    109109    BitField bitfield = storage.getBitField();
    110     for(int i = 0; i < metainfo.getPieces(); i++)
    111       if (!bitfield.get(i))
    112         wantedPieces.add(new Piece(i));
    113     Collections.shuffle(wantedPieces);
     110    int[] pri = storage.getPiecePriorities();
     111    for(int i = 0; i < metainfo.getPieces(); i++) {
     112      if (!bitfield.get(i)) {
     113        Piece p = new Piece(i);
     114        if (pri != null)
     115            p.setPriority(pri[i]);
     116        wantedPieces.add(p);
     117      }
     118    }
     119    Collections.shuffle(wantedPieces, _random);
    114120  }
    115121
     
    521527          {
    522528            Piece p = it.next();
     529            // sorted by priority, so when we hit a disabled piece we are done
     530            if (p.isDisabled())
     531                break;
    523532            if (havePieces.get(p.getId()) && !p.isRequested())
    524533              {
     
    539548                return -1;  // nothing to request and not in end game
    540549            // let's not all get on the same piece
    541             Collections.shuffle(requested);
     550            Collections.shuffle(requested, _random);
    542551            Iterator<Piece> it2 = requested.iterator();
    543552            while (piece == null && it2.hasNext())
     
    564573            }
    565574        }
     575        if (_log.shouldLog(Log.DEBUG))
     576            _log.debug("Now requesting: piece " + piece + " priority " + piece.getPriority());
    566577        piece.setRequested(true);
    567578        return piece.getId();
     579      }
     580  }
     581
     582  /**
     583   *  Maps file priorities to piece priorities.
     584   *  Call after updating file priorities Storage.setPriority()
     585   *  @since 0.8.1
     586   */
     587  public void updatePiecePriorities() {
     588      int[] pri = storage.getPiecePriorities();
     589      if (pri == null)
     590          return;
     591      synchronized(wantedPieces) {
     592          // Add incomplete and previously unwanted pieces to the list
     593          // Temp to avoid O(n**2)
     594          BitField want = new BitField(pri.length);
     595          for (Piece p : wantedPieces) {
     596              want.set(p.getId());
     597          }
     598          BitField bitfield = storage.getBitField();
     599          for (int i = 0; i < pri.length; i++) {
     600              if (pri[i] >= 0 && !bitfield.get(i)) {
     601                  if (!want.get(i)) {
     602                      Piece piece = new Piece(i);
     603                      wantedPieces.add(piece);
     604                      // As connections are already up, new Pieces will
     605                      // not have their PeerID list populated, so do that.
     606                      synchronized(peers) {
     607                          for (Peer p : peers) {
     608                              PeerState s = p.state;
     609                              if (s != null) {
     610                                  BitField bf = s.bitfield;
     611                                  if (bf != null && bf.get(i))
     612                                      piece.addPeer(p);
     613                              }
     614                          }
     615                      }
     616                  }
     617              }
     618          }
     619          // now set the new priorities and remove newly unwanted pieces
     620          for (Iterator<Piece> iter = wantedPieces.iterator(); iter.hasNext(); ) {
     621               Piece p = iter.next();
     622               int id = pri[p.getId()];
     623               if (id >= 0)
     624                   p.setPriority(pri[p.getId()]);
     625               else
     626                   iter.remove();
     627          }
     628          // if we added pieces, they will be in-order unless we shuffle
     629          Collections.shuffle(wantedPieces, _random);
    568630      }
    569631  }
     
    633695            // No need to announce have piece to peers.
    634696            // Assume we got a good piece, we don't really care anymore.
    635             return true;
     697            // Well, this could be caused by a change in priorities, so
     698            // only return true if we already have it, otherwise might as well keep it.
     699            if (storage.getBitField().get(piece))
     700                return true;
    636701          }
    637702       
     
    640705            if (storage.putPiece(piece, bs))
    641706              {
    642                 _log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
     707                if (_log.shouldLog(Log.INFO))
     708                    _log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
    643709              }
    644710            else
  • apps/i2psnark/java/src/org/klomp/snark/Piece.java

    r1aba324 r9afff4f  
    22
    33import java.util.Collections;
    4 import java.util.HashSet;
    54import java.util.Set;
     5
     6import net.i2p.util.ConcurrentHashSet;
    67
    78public class Piece implements Comparable {
    89
    910    private int id;
    10     private Set peers;
     11    private Set<PeerID> peers;
    1112    private boolean requested;
     13    /** @since 0.8.1 */
     14    private int priority;
    1215   
    1316    public Piece(int id) {
    1417        this.id = id;
    15         this.peers = Collections.synchronizedSet(new HashSet());
    16         this.requested = false;
     18        this.peers = new ConcurrentHashSet();
    1719    }
    1820   
     21    /**
     22     *  Highest priority first,
     23     *  then rarest first
     24     */
    1925    public int compareTo(Object o) throws ClassCastException {
     26        int pdiff = ((Piece)o).priority - this.priority;   // reverse
     27        if (pdiff != 0)
     28            return pdiff;
    2029        return this.peers.size() - ((Piece)o).peers.size();
    2130    }
     
    3847   
    3948    public int getId() { return this.id; }
    40     public Set getPeers() { return this.peers; }
     49    /** @deprecated unused */
     50    public Set<PeerID> getPeers() { return this.peers; }
    4151    public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
    4252    public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
     
    4454    public void setRequested(boolean requested) { this.requested = requested; }
    4555   
     56    /** @return default 0 @since 0.8.1 */
     57    public int getPriority() { return this.priority; }
     58
     59    /** @since 0.8.1 */
     60    public void setPriority(int p) { this.priority = p; }
     61
     62    /** @since 0.8.1 */
     63    public boolean isDisabled() { return this.priority < 0; }
     64
     65    /** @since 0.8.1 */
     66    public void setDisabled() { this.priority = -1; }
     67
    4668    @Override
    4769    public String toString() {
  • apps/i2psnark/java/src/org/klomp/snark/Storage.java

    r1aba324 r9afff4f  
    4343  private long[] RAFtime;    // when was RAF last accessed, or 0 if closed
    4444  private File[] RAFfile;    // File to make it easier to reopen
     45  /** priorities by file; default 0; may be null. @since 0.8.1 */
     46  private int[] priorities;
    4547
    4648  private final StorageListener listener;
     
    229231    RAFtime = new long[size];
    230232    RAFfile = new File[size];
     233    priorities = new int[size];
     234
    231235
    232236    int i = 0;
     
    332336
    333337  /**
     338   *  @param file canonical path (non-directory)
     339   *  @since 0.8.1
     340   */
     341  public int getPriority(String file) {
     342      if (complete() || metainfo.getFiles() == null || priorities == null)
     343          return 0;
     344      for (int i = 0; i < rafs.length; i++) {
     345          File f = RAFfile[i];
     346          // use canonical in case snark dir or sub dirs are symlinked
     347          if (f != null) {
     348              try {
     349                  String canonical = f.getCanonicalPath();
     350                  if (canonical.equals(file))
     351                      return priorities[i];
     352              } catch (IOException ioe) {}
     353          }
     354      }
     355      return 0;
     356  }
     357
     358  /**
     359   *  Must call setPiecePriorities() after calling this
     360   *  @param file canonical path (non-directory)
     361   *  @param priority default 0; <0 to disable
     362   *  @since 0.8.1
     363   */
     364  public void setPriority(String file, int pri) {
     365      if (complete() || metainfo.getFiles() == null || priorities == null)
     366          return;
     367      for (int i = 0; i < rafs.length; i++) {
     368          File f = RAFfile[i];
     369          // use canonical in case snark dir or sub dirs are symlinked
     370          if (f != null) {
     371              try {
     372                  String canonical = f.getCanonicalPath();
     373                  if (canonical.equals(file)) {
     374                      priorities[i] = pri;
     375                      return;
     376                  }
     377              } catch (IOException ioe) {}
     378          }
     379      }
     380  }
     381
     382  /**
     383   *  Call setPriority() for all changed files first,
     384   *  then call this.
     385   *  Set the piece priority to the highest priority
     386   *  of all files spanning the piece.
     387   *  Caller must pass array to the PeerCoordinator.
     388   *  @return null on error, if complete, or if only one file
     389   *  @since 0.8.1
     390   */
     391  public int[] getPiecePriorities() {
     392      if (complete() || metainfo.getFiles() == null)
     393          return null;
     394      int[] rv = new int[metainfo.getPieces()];
     395      int file = 0;
     396      long pcEnd = -1;
     397      long fileEnd = lengths[0] - 1;
     398      int psz = metainfo.getPieceLength(0);
     399      for (int i = 0; i < rv.length; i++) {
     400          pcEnd += psz;
     401          int pri = priorities[file];
     402          while (fileEnd <= pcEnd && file < lengths.length - 1) {
     403              file++;
     404              long oldFileEnd = fileEnd;
     405              fileEnd += lengths[file];
     406              if (priorities[file] > pri && pcEnd < oldFileEnd)
     407                  pri = priorities[file];
     408          }
     409          rv[i] = pri;
     410      }
     411      return rv;
     412  }
     413
     414  /**
    334415   * The BitField that tells which pieces this storage contains.
    335416   * Do not change this since this is the current state of the storage.
     
    437518      checkCreateFiles();
    438519    }
    439     if (complete())
     520    if (complete()) {
    440521        _util.debug("Torrent is complete", Snark.NOTICE);
    441     else
     522    } else {
     523        // fixme saved priorities
     524        if (files != null)
     525            priorities = new int[files.size()];
    442526        _util.debug("Still need " + needed + " out of " + metainfo.getPieces() + " pieces", Snark.NOTICE);
     527    }
    443528  }
    444529
     
    625710    listener.storageCreateFile(this, names[nr], lengths[nr]);
    626711    final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
    627     byte[] zeros = new byte[ZEROBLOCKSIZE];
     712    byte[] zeros;
     713    try {
     714        zeros = new byte[ZEROBLOCKSIZE];
     715    } catch (OutOfMemoryError oom) {
     716        throw new IOException(oom.toString());
     717    }
    628718    int i;
    629719    for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
  • apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java

    r1aba324 r9afff4f  
    267267                        // from them (duh)
    268268                        List ordered = new ArrayList(peers);
    269                         Collections.shuffle(ordered);
     269                        Collections.shuffle(ordered, r);
    270270                        Iterator it = ordered.iterator();
    271271                        while (it.hasNext()) {
  • apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java

    r1aba324 r9afff4f  
    134134                String pathInfo = req.getPathInfo();
    135135                String pathInContext = URI.addPaths(path, pathInfo);
     136                req.setCharacterEncoding("UTF-8");
    136137                resp.setCharacterEncoding("UTF-8");
    137138                resp.setContentType("text/html; charset=UTF-8");
     
    141142                } else {
    142143                    String base = URI.addPaths(req.getRequestURI(), "/");
    143                     String listing = getListHTML(resource, base, true);
     144                    String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null);
    144145                    if (listing != null)
    145146                        resp.getWriter().write(listing);
     
    12531254     * @param base The base URL
    12541255     * @param parent True if the parent directory should be included
     1256     * @param postParams map of POST parameters or null if not a POST
    12551257     * @return String of HTML
    12561258     * @since 0.7.14
    12571259     */
    1258     private String getListHTML(Resource r, String base, boolean parent)
     1260    private String getListHTML(Resource r, String base, boolean parent, Map postParams)
    12591261        throws IOException
    12601262    {
     
    12811283            torrentName = title;
    12821284        Snark snark = _manager.getTorrentByBaseName(torrentName);
     1285
     1286        if (snark != null && postParams != null)
     1287            savePriorities(snark, postParams);
     1288
    12831289        if (title.endsWith("/"))
    12841290            title = title.substring(0, title.length() - 1);
     
    12981304        }
    12991305       
    1300         buf.append("</div><div class=\"page\"><div class=\"mainsection\">" +
    1301                    "<TABLE BORDER=0 class=\"snarkTorrents\" cellpadding=\"5px 10px\">" +
     1306        buf.append("</div><div class=\"page\"><div class=\"mainsection\">");
     1307        boolean showPriority = snark != null && !snark.storage.complete();
     1308        if (showPriority)
     1309            buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
     1310        buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" cellpadding=\"5px 10px\">" +
    13021311                   "<thead><tr><th>").append(_("File")).append("</th><th>").append(_("Size"))
    1303            .append("</th><th>").append(_("Status")).append("</th></tr></thead>");
     1312           .append("</th><th>").append(_("Status")).append("</th>");
     1313        if (showPriority)
     1314            buf.append("<th>").append(_("Priority")).append("</th>");
     1315        buf.append("</tr></thead>\n");
    13041316        //DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
    13051317        //                                               DateFormat.MEDIUM);
     1318        boolean showSaveButton = false;
    13061319        for (int i=0 ; i< ls.length ; i++)
    13071320        {   
     
    13411354                                status = toImg("tick") + _("Complete");
    13421355                            } else {
    1343                                 status = toImg("clock") +
     1356                                status =
     1357                                         (snark.storage.getPriority(f.getCanonicalPath()) < 0 ? toImg("cancel") : toImg("clock")) +
    13441358                                         (100 * (length - remaining) / length) + "% " + _("complete") +
    13451359                                         " (" + DataHelper.formatSize2(remaining) + _("bytes remaining") + ")";
     
    13851399            //buf.append(dfmt.format(new Date(item.lastModified())));
    13861400            buf.append(status);
    1387             buf.append("</TD></TR>\n");
     1401            buf.append("</TD>");
     1402            if (showPriority) {
     1403                buf.append("<td>");
     1404                File f = item.getFile();
     1405                if ((!complete) && (!item.isDirectory()) && f != null) {
     1406                    int pri = snark.storage.getPriority(f.getCanonicalPath());
     1407                    buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
     1408                    if (pri > 0)
     1409                        buf.append("checked=\"true\"");
     1410                    buf.append('>').append(_("High"));
     1411
     1412                    buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
     1413                    if (pri == 0)
     1414                        buf.append("checked=\"true\"");
     1415                    buf.append('>').append(_("Normal"));
     1416
     1417                    buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
     1418                    if (pri < 0)
     1419                        buf.append("checked=\"true\"");
     1420                    buf.append('>').append(_("Do not download"));
     1421                    showSaveButton = true;
     1422                }
     1423                buf.append("</td>");
     1424            }
     1425            buf.append("</TR>\n");
     1426        }
     1427        if (showSaveButton) {
     1428            buf.append("<thead><tr><th colspan=\"3\">&nbsp;</th><th align=\"center\"><input type=\"submit\" value=\"");
     1429            buf.append(_("Save priorities"));
     1430            buf.append("\" name=\"foo\" ></th></tr></thead>\n");
    13881431        }
    13891432        buf.append("</TABLE>\n");
     1433        if (showPriority)
     1434            buf.append("</form>");
    13901435        buf.append("</div></div></BODY></HTML>\n");
    13911436       
     
    14531498    }
    14541499
     1500    /** @since 0.8.1 */
     1501    private static void savePriorities(Snark snark, Map postParams) {
     1502        Set<Map.Entry> entries = postParams.entrySet();
     1503        for (Map.Entry entry : entries) {
     1504            String key = (String)entry.getKey();
     1505            if (key.startsWith("pri.")) {
     1506                try {
     1507                    String file = key.substring(4);
     1508                    String val = ((String[])entry.getValue())[0];   // jetty arrays
     1509                    int pri = Integer.parseInt(val);
     1510                    snark.storage.setPriority(file, pri);
     1511                    //System.err.println("Priority now " + pri + " for " + file);
     1512                } catch (Throwable t) { t.printStackTrace(); }
     1513            }
     1514        }
     1515        if (snark.coordinator != null)
     1516            snark.coordinator.updatePiecePriorities();
     1517    }
     1518
    14551519
    14561520/** inner class, don't bother reindenting */
Note: See TracChangeset for help on using the changeset viewer.