diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 6f17a810ab..8edc92fdf3 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -152,6 +152,9 @@ public class I2PSnarkUtil { */ synchronized public boolean connect() { if (_manager == null) { + // try to find why reconnecting after stop + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Connecting to I2P", new Exception("I did it")); Properties opts = new Properties(); if (_opts != null) { for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) { @@ -163,6 +166,10 @@ public class I2PSnarkUtil { opts.setProperty("inbound.nickname", "I2PSnark"); if (opts.getProperty("outbound.nickname") == null) opts.setProperty("outbound.nickname", "I2PSnark"); + // Dont do this for now, it is set in I2PSocketEepGet for announces, + // we don't need fast handshake for peer connections. + //if (opts.getProperty("i2p.streaming.connectDelay") == null) + // opts.setProperty("i2p.streaming.connectDelay", "500"); if (opts.getProperty("i2p.streaming.inactivityTimeout") == null) opts.setProperty("i2p.streaming.inactivityTimeout", "240000"); if (opts.getProperty("i2p.streaming.inactivityAction") == null) @@ -186,6 +193,7 @@ public class I2PSnarkUtil { */ public void disconnect() { I2PSocketManager mgr = _manager; + // FIXME this can cause race NPEs elsewhere _manager = null; _shitlist.clear(); mgr.destroySocketManager(); @@ -197,6 +205,9 @@ public class I2PSnarkUtil { /** connect to the given destination */ I2PSocket connect(PeerID peer) throws IOException { + I2PSocketManager mgr = _manager; + if (mgr == null) + throw new IOException("No socket manager"); Destination addr = peer.getAddress(); if (addr == null) throw new IOException("Null address"); diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index da1003aaba..55b6dcb2ca 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -56,8 +56,8 @@ public class Peer implements Comparable private long _id; final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long - private long uploaded_old[] = {-1,-1,-1,-1,-1,-1}; - private long downloaded_old[] = {-1,-1,-1,-1,-1,-1}; + private long uploaded_old[] = {-1,-1,-1}; + private long downloaded_old[] = {-1,-1,-1}; /** * Creates a disconnected peer given a PeerID, your own id and the @@ -117,10 +117,15 @@ public class Peer implements Comparable } /** - * Returns socket (for debug printing) + * @return socket debug string (for debug printing) */ public String getSocket() { + if (state != null) { + String r = state.getRequests(); + if (r != null) + return sock.toString() + "
Requests: " + r; + } return sock.toString(); } @@ -387,6 +392,37 @@ public class Peer implements Comparable s.havePiece(piece); } + /** + * Tell the other side that we are no longer interested in any of + * the outstanding requests (if any) for this piece. + * @since 0.8.1 + */ + void cancel(int piece) { + PeerState s = state; + if (s != null) + s.cancelPiece(piece); + } + + /** + * Are we currently requesting the piece? + * @since 0.8.1 + */ + boolean isRequesting(int p) { + PeerState s = state; + return s != null && s.isRequesting(p); + } + + /** + * Update the request queue. + * Call after adding wanted pieces. + * @since 0.8.1 + */ + void request() { + PeerState s = state; + if (s != null) + s.addRequest(); + } + /** * Whether or not the peer is interested in pieces we have. Returns * false if not connected. @@ -545,17 +581,8 @@ public class Peer implements Comparable */ public void setRateHistory(long up, long down) { - setRate(up, uploaded_old); - setRate(down, downloaded_old); - } - - private void setRate(long val, long array[]) - { - synchronized(array) { - for (int i = RATE_DEPTH-1; i > 0; i--) - array[i] = array[i-1]; - array[0] = val; - } + PeerCoordinator.setRate(up, uploaded_old); + PeerCoordinator.setRate(down, downloaded_old); } /** @@ -563,28 +590,11 @@ public class Peer implements Comparable */ public long getUploadRate() { - return getRate(uploaded_old); + return PeerCoordinator.getRate(uploaded_old); } public long getDownloadRate() { - return getRate(downloaded_old); + return PeerCoordinator.getRate(downloaded_old); } - - private long getRate(long array[]) - { - long rate = 0; - int i = 0; - synchronized(array) { - for ( ; i < RATE_DEPTH; i++){ - if (array[i] < 0) - break; - rate += array[i]; - } - } - if (i == 0) - return 0; - return rate / (i * CHECK_PERIOD / 1000); - } - } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 8f24c864ac..c8f8d7d7ff 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -57,9 +57,9 @@ public class PeerCoordinator implements PeerListener private long uploaded; private long downloaded; - final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long - private long uploaded_old[] = {-1,-1,-1,-1,-1,-1}; - private long downloaded_old[] = {-1,-1,-1,-1,-1,-1}; + final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long + private long uploaded_old[] = {-1,-1,-1}; + private long downloaded_old[] = {-1,-1,-1}; // synchronize on this when changing peers or downloaders final List peers = new ArrayList(); @@ -78,6 +78,7 @@ public class PeerCoordinator implements PeerListener private final CoordinatorListener listener; public I2PSnarkUtil _util; + private static final Random _random = I2PAppContext.getGlobalContext().random(); public String trackerProblems = null; public int trackerSeenPeers = 0; @@ -97,20 +98,29 @@ public class PeerCoordinator implements PeerListener // Install a timer to check the uploaders. // Randomize the first start time so multiple tasks are spread out, // this will help the behavior with global limits - Random r = I2PAppContext.getGlobalContext().random(); - timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD); + timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + _random.nextInt((int) CHECK_PERIOD), CHECK_PERIOD); } // only called externally from Storage after the double-check fails public void setWantedPieces() { // Make a list of pieces + // FIXME synchronize, clear and re-add instead? + // Don't replace something we are synchronizing on. wantedPieces = new ArrayList(); BitField bitfield = storage.getBitField(); - for(int i = 0; i < metainfo.getPieces(); i++) - if (!bitfield.get(i)) - wantedPieces.add(new Piece(i)); - Collections.shuffle(wantedPieces); + int[] pri = storage.getPiecePriorities(); + for(int i = 0; i < metainfo.getPieces(); i++) { + // only add if we don't have and the priority is >= 0 + if ((!bitfield.get(i)) && + (pri == null || pri[i] >= 0)) { + Piece p = new Piece(i); + if (pri != null) + p.setPriority(pri[i]); + wantedPieces.add(p); + } + } + Collections.shuffle(wantedPieces, _random); } public Storage getStorage() { return storage; } @@ -183,7 +193,7 @@ public class PeerCoordinator implements PeerListener setRate(down, downloaded_old); } - private static void setRate(long val, long array[]) + static void setRate(long val, long array[]) { synchronized(array) { for (int i = RATE_DEPTH-1; i > 0; i--) @@ -214,20 +224,23 @@ public class PeerCoordinator implements PeerListener return (r * 1000) / CHECK_PERIOD; } - private long getRate(long array[]) + static long getRate(long array[]) { long rate = 0; int i = 0; + int factor = 0; synchronized(array) { for ( ; i < RATE_DEPTH; i++) { if (array[i] < 0) break; - rate += array[i]; + int f = RATE_DEPTH - i; + rate += array[i] * f; + factor += f; } } if (i == 0) return 0; - return rate / (i * CHECK_PERIOD / 1000); + return rate / (factor * CHECK_PERIOD / 1000); } public MetaInfo getMetaInfo() @@ -454,7 +467,7 @@ public class PeerCoordinator implements PeerListener } /** - * Returns true if we don't have the given piece yet. + * @return true if we still want the given piece */ public boolean gotHave(Peer peer, int piece) { @@ -499,6 +512,12 @@ public class PeerCoordinator implements PeerListener */ private static final int END_GAME_THRESHOLD = 8; + /** + * Max number of peers to get a piece from when in end game + * @since 0.8.1 + */ + private static final int MAX_PARALLEL_REQUESTS = 4; + /** * Returns one of pieces in the given BitField that is still wanted or * -1 if none of the given pieces are wanted. @@ -520,6 +539,9 @@ public class PeerCoordinator implements PeerListener while (piece == null && it.hasNext()) { Piece p = it.next(); + // sorted by priority, so when we hit a disabled piece we are done + if (p.isDisabled()) + break; if (havePieces.get(p.getId()) && !p.isRequested()) { piece = p; @@ -538,15 +560,32 @@ public class PeerCoordinator implements PeerListener if (wantedPieces.size() > END_GAME_THRESHOLD) return -1; // nothing to request and not in end game // let's not all get on the same piece - Collections.shuffle(requested); + // Even better would be to sort by number of requests + Collections.shuffle(requested, _random); Iterator it2 = requested.iterator(); while (piece == null && it2.hasNext()) { Piece p = it2.next(); - if (havePieces.get(p.getId())) - { + if (havePieces.get(p.getId())) { + // limit number of parallel requests + int requestedCount = 0; + synchronized(peers) { + for (Peer pr : peers) { + if (pr.isRequesting(p.getId())) { + if (pr.equals(peer)) { + // don't give it to him again + requestedCount = MAX_PARALLEL_REQUESTS; + break; + } + if (++requestedCount >= MAX_PARALLEL_REQUESTS) + break; + } + } + } + if (requestedCount >= MAX_PARALLEL_REQUESTS) + continue; piece = p; - } + } } if (piece == null) { if (_log.shouldLog(Log.WARN)) @@ -555,7 +594,7 @@ public class PeerCoordinator implements PeerListener // + " wanted = " + wantedPieces + " peerHas = " + havePieces); return -1; //If we still can't find a piece we want, so be it. } else { - // Should be a lot smarter here - limit # of parallel attempts and + // Should be a lot smarter here - // share blocks rather than starting from 0 with each peer. // This is where the flaws of the snark data model are really exposed. // Could also randomize within the duplicate set rather than strict rarest-first @@ -563,11 +602,83 @@ public class PeerCoordinator implements PeerListener _log.debug("parallel request (end game?) for " + peer + ": piece = " + piece); } } + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Now requesting: piece " + piece + " priority " + piece.getPriority()); piece.setRequested(true); return piece.getId(); } } + /** + * Maps file priorities to piece priorities. + * Call after updating file priorities Storage.setPriority() + * @since 0.8.1 + */ + public void updatePiecePriorities() { + int[] pri = storage.getPiecePriorities(); + if (pri == null) { + _log.debug("Updated piece priorities called but no priorities to set?"); + return; + } + synchronized(wantedPieces) { + // Add incomplete and previously unwanted pieces to the list + // Temp to avoid O(n**2) + BitField want = new BitField(pri.length); + for (Piece p : wantedPieces) { + want.set(p.getId()); + } + BitField bitfield = storage.getBitField(); + for (int i = 0; i < pri.length; i++) { + if (pri[i] >= 0 && !bitfield.get(i)) { + if (!want.get(i)) { + Piece piece = new Piece(i); + wantedPieces.add(piece); + // As connections are already up, new Pieces will + // not have their PeerID list populated, so do that. + synchronized(peers) { + for (Peer p : peers) { + PeerState s = p.state; + if (s != null) { + BitField bf = s.bitfield; + if (bf != null && bf.get(i)) + piece.addPeer(p); + } + } + } + } + } + } + // now set the new priorities and remove newly unwanted pieces + for (Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) { + Piece p = iter.next(); + int priority = pri[p.getId()]; + if (priority >= 0) { + p.setPriority(priority); + } else { + iter.remove(); + // cancel all peers + synchronized(peers) { + for (Peer peer : peers) { + peer.cancel(p.getId()); + } + } + } + } + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Updated piece priorities, now wanted: " + wantedPieces); + // if we added pieces, they will be in-order unless we shuffle + Collections.shuffle(wantedPieces, _random); + + // update request queues, in case we added wanted pieces + // and we were previously uninterested + synchronized(peers) { + for (Peer peer : peers) { + peer.request(); + } + } + } + } + /** * Returns a byte array containing the requested piece or null of * the piece is unknown. @@ -632,14 +743,18 @@ public class PeerCoordinator implements PeerListener // No need to announce have piece to peers. // Assume we got a good piece, we don't really care anymore. - return true; + // Well, this could be caused by a change in priorities, so + // only return true if we already have it, otherwise might as well keep it. + if (storage.getBitField().get(piece)) + return true; } try { if (storage.putPiece(piece, bs)) { - _log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName()); + if (_log.shouldLog(Log.INFO)) + _log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName()); } else { diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java index 5e9250e22d..4d34736a89 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java @@ -55,12 +55,10 @@ class PeerState final PeerConnectionOut out; // Outstanding request - private final List outstandingRequests = new ArrayList(); + private final List outstandingRequests = new ArrayList(); + /** the tail (NOT the head) of the request queue */ private Request lastRequest = null; - // If we have te resend outstanding requests (true after we got choked). - private boolean resend = false; - private final static int MAX_PIPELINE = 5; // this is for outbound requests private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests public final static int PARTSIZE = 16*1024; // outbound request @@ -91,14 +89,13 @@ class PeerState if (_log.shouldLog(Log.DEBUG)) _log.debug(peer + " rcv " + (choke ? "" : "un") + "choked"); + boolean resend = choked && !choke; choked = choke; - if (choked) - resend = true; listener.gotChoke(peer, choke); - if (!choked && interesting) - request(); + if (interesting && !choked) + request(resend); } void interestedMessage(boolean interest) @@ -278,7 +275,7 @@ class PeerState synchronized private int getFirstOutstandingRequest(int piece) { for (int i = 0; i < outstandingRequests.size(); i++) - if (((Request)outstandingRequests.get(i)).piece == piece) + if (outstandingRequests.get(i).piece == piece) return i; return -1; } @@ -313,12 +310,12 @@ class PeerState Request req; synchronized(this) { - req = (Request)outstandingRequests.get(r); + req = outstandingRequests.get(r); while (req.piece == piece && req.off != begin && r < outstandingRequests.size() - 1) { r++; - req = (Request)outstandingRequests.get(r); + req = outstandingRequests.get(r); } // Something wrong? @@ -342,7 +339,7 @@ class PeerState + ", wanted for peer: " + peer); for (int i = 0; i < r; i++) { - Request dropReq = (Request)outstandingRequests.remove(0); + Request dropReq = outstandingRequests.remove(0); outstandingRequests.add(dropReq); if (!choked) out.sendRequest(dropReq); @@ -366,11 +363,11 @@ class PeerState { Request req = null; for (int i = 0; i < outstandingRequests.size(); i++) { - Request r1 = (Request)outstandingRequests.get(i); + Request r1 = outstandingRequests.get(i); int j = getFirstOutstandingRequest(r1.piece); if (j == -1) continue; - Request r2 = (Request)outstandingRequests.get(j); + Request r2 = outstandingRequests.get(j); if (r2.off > 0 && ((req == null) || (r2.off > req.off))) req = r2; } @@ -398,7 +395,7 @@ class PeerState } Request req = null; for (int i = 0; i < size; i++) { - Request r1 = (Request)outstandingRequests.get(i); + Request r1 = outstandingRequests.get(i); if (pc != r1.piece) { pc = r1.piece; arr[pos++] = pc; @@ -423,32 +420,19 @@ class PeerState + " length: " + bs.length); } + /** + * We now have this piece. + * Tell the peer and cancel any requests for the piece. + */ void havePiece(int piece) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Tell " + peer + " havePiece(" + piece + ")"); - synchronized(this) - { // Tell the other side that we are no longer interested in any of // the outstanding requests for this piece. - if (lastRequest != null && lastRequest.piece == piece) - lastRequest = null; - - Iterator it = outstandingRequests.iterator(); - while (it.hasNext()) - { - Request req = (Request)it.next(); - if (req.piece == piece) - { - it.remove(); - // Send cancel even when we are choked to make sure that it is - // really never ever send. - out.sendCancel(req); - } - } - } - + cancelPiece(piece); + // Tell the other side that we really have this piece. out.sendHave(piece); @@ -463,8 +447,46 @@ class PeerState } } - // Starts or resumes requesting pieces. - private void request() + /** + * Tell the other side that we are no longer interested in any of + * the outstanding requests (if any) for this piece. + * @since 0.8.1 + */ + synchronized void cancelPiece(int piece) { + if (lastRequest != null && lastRequest.piece == piece) + lastRequest = null; + + Iterator it = outstandingRequests.iterator(); + while (it.hasNext()) + { + Request req = it.next(); + if (req.piece == piece) + { + it.remove(); + // Send cancel even when we are choked to make sure that it is + // really never ever send. + out.sendCancel(req); + } + } + } + + /** + * Are we currently requesting the piece? + * @since 0.8.1 + */ + synchronized boolean isRequesting(int piece) { + for (Request req : outstandingRequests) { + if (req.piece == piece) + return true; + } + return false; + } + + /** + * Starts or resumes requesting pieces. + * @param resend should we resend outstanding requests? + */ + private void request(boolean resend) { // Are there outstanding requests that have to be resend? if (resend) @@ -472,7 +494,6 @@ class PeerState synchronized (this) { out.sendRequests(outstandingRequests); } - resend = false; } // Add/Send some more requests if necessary. @@ -481,8 +502,11 @@ class PeerState /** * Adds a new request to the outstanding requests list. + * Then send interested if we weren't. + * Then send new requests if not choked. + * If nothing to request, send not interested if we were. */ - synchronized private void addRequest() + synchronized void addRequest() { boolean more_pieces = true; while (more_pieces) @@ -526,6 +550,7 @@ class PeerState /** * Starts requesting first chunk of next piece. Returns true if * something has been added to the requests, false otherwise. + * Caller should synchronize. */ private boolean requestNextPiece() { @@ -553,11 +578,10 @@ class PeerState } } int nextPiece = listener.wantPiece(peer, bitfield); - if (_log.shouldLog(Log.DEBUG)) - _log.debug(peer + " want piece " + nextPiece); - if (nextPiece != -1 - && (lastRequest == null || lastRequest.piece != nextPiece)) - { + if (nextPiece != -1 + && (lastRequest == null || lastRequest.piece != nextPiece)) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug(peer + " want piece " + nextPiece); // Fail safe to make sure we are interested // When we transition into the end game we may not be interested... if (!interesting) { @@ -584,9 +608,25 @@ class PeerState out.sendRequest(req); lastRequest = req; return true; - } + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug(peer + " no more pieces to request"); + } } + // failsafe + if (outstandingRequests.isEmpty()) + lastRequest = null; + + // If we are not in the end game, we may run out of things to request + // because we are asking other peers. Set not-interesting now rather than + // wait for those other requests to be satisfied via havePiece() + if (interesting && lastRequest == null) { + interesting = false; + out.sendInterest(false); + if (_log.shouldLog(Log.DEBUG)) + _log.debug(peer + " nothing more to request, now uninteresting"); + } return false; } @@ -601,7 +641,7 @@ class PeerState out.sendInterest(interest); if (interesting && !choked) - request(); + request(true); // we shouldnt have any pending requests, but if we do, resend them } } @@ -627,4 +667,16 @@ class PeerState if (interesting && !choked) out.retransmitRequests(outstandingRequests); } + + /** + * debug + * @return string or null + * @since 0.8.1 + */ + synchronized String getRequests() { + if (outstandingRequests.isEmpty()) + return null; + else + return outstandingRequests.toString(); + } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/Piece.java b/apps/i2psnark/java/src/org/klomp/snark/Piece.java index 3fd0771f98..0ae9570e13 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Piece.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Piece.java @@ -1,22 +1,31 @@ package org.klomp.snark; import java.util.Collections; -import java.util.HashSet; import java.util.Set; +import net.i2p.util.ConcurrentHashSet; + public class Piece implements Comparable { private int id; - private Set peers; + private Set peers; private boolean requested; + /** @since 0.8.1 */ + private int priority; public Piece(int id) { this.id = id; - this.peers = Collections.synchronizedSet(new HashSet()); - this.requested = false; + this.peers = new ConcurrentHashSet(); } + /** + * Highest priority first, + * then rarest first + */ public int compareTo(Object o) throws ClassCastException { + int pdiff = ((Piece)o).priority - this.priority; // reverse + if (pdiff != 0) + return pdiff; return this.peers.size() - ((Piece)o).peers.size(); } @@ -37,12 +46,25 @@ public class Piece implements Comparable { } public int getId() { return this.id; } - public Set getPeers() { return this.peers; } + /** @deprecated unused */ + public Set getPeers() { return this.peers; } public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); } public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); } public boolean isRequested() { return this.requested; } public void setRequested(boolean requested) { this.requested = requested; } + /** @return default 0 @since 0.8.1 */ + public int getPriority() { return this.priority; } + + /** @since 0.8.1 */ + public void setPriority(int p) { this.priority = p; } + + /** @since 0.8.1 */ + public boolean isDisabled() { return this.priority < 0; } + + /** @since 0.8.1 */ + public void setDisabled() { this.priority = -1; } + @Override public String toString() { return String.valueOf(id); diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index b0fe6d1ba7..240515e595 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -54,6 +54,7 @@ public class SnarkManager implements Snark.CompleteListener { public static final String PROP_DIR = "i2psnark.dir"; public static final String PROP_META_PREFIX = "i2psnark.zmeta."; public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; + public static final String PROP_META_PRIORITY_SUFFIX = ".priority"; private static final String CONFIG_FILE = "i2psnark.config"; public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops @@ -510,6 +511,7 @@ public class SnarkManager implements Snark.CompleteListener { torrent = new Snark(_util, filename, null, -1, null, null, this, _peerCoordinatorSet, _connectionAcceptor, false, dataDir.getPath()); + loadSavedFilePriorities(torrent); torrent.completeListener = this; synchronized (_snarks) { _snarks.put(filename, torrent); @@ -587,6 +589,33 @@ public class SnarkManager implements Snark.CompleteListener { return new BitField(bitfield, len); } + /** + * Get the saved priorities for a torrent from the config file. + * @since 0.8.1 + */ + public void loadSavedFilePriorities(Snark snark) { + MetaInfo metainfo = snark.meta; + if (metainfo.getFiles() == null) + return; + byte[] ih = metainfo.getInfoHash(); + String infohash = Base64.encode(ih); + infohash = infohash.replace('=', '$'); + String pri = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX); + if (pri == null) + return; + int filecount = metainfo.getFiles().size(); + int[] rv = new int[filecount]; + String[] arr = pri.split(","); + for (int i = 0; i < filecount && i < arr.length; i++) { + if (arr[i].length() > 0) { + try { + rv[i] = Integer.parseInt(arr[i]); + } catch (Throwable t) {} + } + } + snark.storage.setFilePriorities(rv); + } + /** * Save the completion status of a torrent and the current time in the config file * in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield". @@ -595,8 +624,9 @@ public class SnarkManager implements Snark.CompleteListener { * The time is a standard long converted to string. * The status is either a bitfield converted to Base64 or "." for a completed * torrent to save space in the config file and in memory. + * @param priorities may be null */ - public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) { + public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) { byte[] ih = metainfo.getInfoHash(); String infohash = Base64.encode(ih); infohash = infohash.replace('=', '$'); @@ -609,6 +639,34 @@ public class SnarkManager implements Snark.CompleteListener { bfs = Base64.encode(bf); } _config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs); + + // now the file priorities + String prop = PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX; + if (priorities != null) { + boolean nonzero = false; + for (int i = 0; i < priorities.length; i++) { + if (priorities[i] != 0) { + nonzero = true; + break; + } + } + if (nonzero) { + // generate string like -5,,4,3,,,,,,-2 where no number is zero. + StringBuilder buf = new StringBuilder(2 * priorities.length); + for (int i = 0; i < priorities.length; i++) { + if (priorities[i] != 0) + buf.append(Integer.toString(priorities[i])); + if (i != priorities.length - 1) + buf.append(','); + } + _config.setProperty(prop, buf.toString()); + } else { + _config.remove(prop); + } + } else { + _config.remove(prop); + } + saveConfig(); } @@ -621,6 +679,7 @@ public class SnarkManager implements Snark.CompleteListener { String infohash = Base64.encode(ih); infohash = infohash.replace('=', '$'); _config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX); + _config.remove(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX); saveConfig(); } @@ -742,7 +801,7 @@ public class SnarkManager implements Snark.CompleteListener { } public void updateStatus(Snark snark) { - saveTorrentStatus(snark.meta, snark.storage.getBitField()); + saveTorrentStatus(snark.meta, snark.storage.getBitField(), snark.storage.getFilePriorities()); } private void monitorTorrents(File dir) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index f90301848d..e77d044324 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -42,6 +42,8 @@ public class Storage private Object[] RAFlock; // lock on RAF access private long[] RAFtime; // when was RAF last accessed, or 0 if closed private File[] RAFfile; // File to make it easier to reopen + /** priorities by file; default 0; may be null. @since 0.8.1 */ + private int[] priorities; private final StorageListener listener; private I2PSnarkUtil _util; @@ -228,6 +230,8 @@ public class Storage RAFlock = new Object[size]; RAFtime = new long[size]; RAFfile = new File[size]; + priorities = new int[size]; + int i = 0; Iterator it = files.iterator(); @@ -330,6 +334,102 @@ public class Storage return -1; } + /** + * @param file canonical path (non-directory) + * @since 0.8.1 + */ + public int getPriority(String file) { + if (complete() || metainfo.getFiles() == null || priorities == null) + return 0; + for (int i = 0; i < rafs.length; i++) { + File f = RAFfile[i]; + // use canonical in case snark dir or sub dirs are symlinked + if (f != null) { + try { + String canonical = f.getCanonicalPath(); + if (canonical.equals(file)) + return priorities[i]; + } catch (IOException ioe) {} + } + } + return 0; + } + + /** + * Must call setPiecePriorities() after calling this + * @param file canonical path (non-directory) + * @param priority default 0; <0 to disable + * @since 0.8.1 + */ + public void setPriority(String file, int pri) { + if (complete() || metainfo.getFiles() == null || priorities == null) + return; + for (int i = 0; i < rafs.length; i++) { + File f = RAFfile[i]; + // use canonical in case snark dir or sub dirs are symlinked + if (f != null) { + try { + String canonical = f.getCanonicalPath(); + if (canonical.equals(file)) { + priorities[i] = pri; + return; + } + } catch (IOException ioe) {} + } + } + } + + /** + * Get the file priorities array. + * @return null on error, if complete, or if only one file + * @since 0.8.1 + */ + public int[] getFilePriorities() { + return priorities; + } + + /** + * Set the file priorities array. + * Only call this when stopped, but after check() + * @param p may be null + * @since 0.8.1 + */ + void setFilePriorities(int[] p) { + priorities = p; + } + + /** + * Call setPriority() for all changed files first, + * then call this. + * Set the piece priority to the highest priority + * of all files spanning the piece. + * Caller must pass array to the PeerCoordinator. + * @return null on error, if complete, or if only one file + * @since 0.8.1 + */ + public int[] getPiecePriorities() { + if (complete() || metainfo.getFiles() == null || priorities == null) + return null; + int[] rv = new int[metainfo.getPieces()]; + int file = 0; + long pcEnd = -1; + long fileEnd = lengths[0] - 1; + int psz = metainfo.getPieceLength(0); + for (int i = 0; i < rv.length; i++) { + pcEnd += psz; + int pri = priorities[file]; + while (fileEnd <= pcEnd && file < lengths.length - 1) { + file++; + long oldFileEnd = fileEnd; + fileEnd += lengths[file]; + if (priorities[file] > pri && oldFileEnd < pcEnd) + pri = priorities[file]; + } + rv[i] = pri; + } + return rv; + } + /** * The BitField that tells which pieces this storage contains. * Do not change this since this is the current state of the storage. @@ -436,10 +536,14 @@ public class Storage changed = true; checkCreateFiles(); } - if (complete()) + if (complete()) { _util.debug("Torrent is complete", Snark.NOTICE); - else + } else { + // fixme saved priorities + if (files != null) + priorities = new int[files.size()]; _util.debug("Still need " + needed + " out of " + metainfo.getPieces() + " pieces", Snark.NOTICE); + } } /** @@ -565,6 +669,10 @@ public class Storage changed = true; synchronized(RAFlock[i]) { allocateFile(i); + // close as we go so we don't run out of file descriptors + try { + closeRAF(i); + } catch (IOException ioe) {} } } else { _util.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR); @@ -573,8 +681,10 @@ public class Storage synchronized(RAFlock[i]) { checkRAF(i); rafs[i].setLength(lengths[i]); + try { + closeRAF(i); + } catch (IOException ioe) {} } - // will be closed below } } @@ -583,10 +693,25 @@ public class Storage { pieces = metainfo.getPieces(); byte[] piece = new byte[metainfo.getPieceLength(0)]; + int file = 0; + long fileEnd = lengths[0]; + long pieceEnd = 0; for (int i = 0; i < pieces; i++) { int length = getUncheckedPiece(i, piece); boolean correctHash = metainfo.checkPiece(i, piece, 0, length); + // close as we go so we don't run out of file descriptors + pieceEnd += length; + while (fileEnd <= pieceEnd) { + synchronized(RAFlock[file]) { + try { + closeRAF(file); + } catch (IOException ioe) {} + } + if (++file >= rafs.length) + break; + fileEnd += lengths[file]; + } if (correctHash) { bitfield.set(i); @@ -601,13 +726,14 @@ public class Storage _probablyComplete = complete(); // close all the files so we don't end up with a zillion open ones; // we will reopen as needed - for (int i = 0; i < rafs.length; i++) { - synchronized(RAFlock[i]) { - try { - closeRAF(i); - } catch (IOException ioe) {} - } - } + // Now closed above to avoid running out of file descriptors + //for (int i = 0; i < rafs.length; i++) { + // synchronized(RAFlock[i]) { + // try { + // closeRAF(i); + // } catch (IOException ioe) {} + // } + //} if (listener != null) { listener.storageAllChecked(this); @@ -616,6 +742,7 @@ public class Storage } } + /** this calls openRAF(); caller must synnchronize and call closeRAF() */ private void allocateFile(int nr) throws IOException { // caller synchronized @@ -624,7 +751,12 @@ public class Storage // the whole file? listener.storageCreateFile(this, names[nr], lengths[nr]); final int ZEROBLOCKSIZE = metainfo.getPieceLength(0); - byte[] zeros = new byte[ZEROBLOCKSIZE]; + byte[] zeros; + try { + zeros = new byte[ZEROBLOCKSIZE]; + } catch (OutOfMemoryError oom) { + throw new IOException(oom.toString()); + } int i; for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index 05df351c69..e3cc8ae9f0 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -266,9 +266,9 @@ public class TrackerClient extends I2PAppThread // we only want to talk to new people if we need things // from them (duh) List ordered = new ArrayList(peers); - Collections.shuffle(ordered); + Collections.shuffle(ordered, r); Iterator it = ordered.iterator(); - while (it.hasNext()) { + while ((!stop) && it.hasNext()) { Peer cur = (Peer)it.next(); // FIXME if id == us || dest == us continue; // only delay if we actually make an attempt to add peer @@ -357,7 +357,7 @@ public class TrackerClient extends I2PAppThread + "&uploaded=" + uploaded + "&downloaded=" + downloaded + "&left=" + left - + "&compact" + + "&compact=1" // NOTE: opentracker will return 400 for &compact alone + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : ""); if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) s += "&numwant=0"; diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 0f4ae8cfe0..8f98aa3ae9 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -133,6 +133,7 @@ public class I2PSnarkServlet extends Default { // bypass the horrid Resource.getListHTML() String pathInfo = req.getPathInfo(); String pathInContext = URI.addPaths(path, pathInfo); + req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html; charset=UTF-8"); Resource resource = getResource(pathInContext); @@ -140,7 +141,7 @@ public class I2PSnarkServlet extends Default { resp.sendError(HttpResponse.__404_Not_Found); } else { String base = URI.addPaths(req.getRequestURI(), "/"); - String listing = getListHTML(resource, base, true); + String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null); if (listing != null) resp.getWriter().write(listing); else // shouldn't happen @@ -241,7 +242,7 @@ public class I2PSnarkServlet extends Default { out.write(TABLE_HEADER); out.write(""); out.write(_("Status")); if (_manager.util().connected() && !snarks.isEmpty()) { @@ -249,24 +250,24 @@ public class I2PSnarkServlet extends Default { out.write(req.getRequestURI()); if (peerParam != null) { out.write("\">"); - out.write("\"");"); + out.write("\"");"); } else { out.write("?p=1\">"); - out.write("\"");"); + out.write("\"");"); } out.write("
\n"); } out.write("\n"); out.write(""); out.write(_("Torrent")); out.write("\n"); @@ -276,21 +277,21 @@ public class I2PSnarkServlet extends Default { out.write(_("ETA")); out.write("\n"); out.write(""); out.write(_("RX")); out.write("\n"); out.write(""); out.write(_("TX")); out.write("\n"); out.write("Rate"); out.write("\n"); out.write(""); out.write(_("Rate")); out.write("\n"); @@ -301,7 +302,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Stop all torrents and the I2P tunnel")); out.write("\">"); out.write("\"");"); @@ -312,7 +313,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Start all torrents and the I2P tunnel")); out.write("\">"); out.write("\"Start"); out.write(""); } else { @@ -537,7 +538,7 @@ public class I2PSnarkServlet extends Default { File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent"); if (torrentFile.exists()) throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath()); - _manager.saveTorrentStatus(info, s.getBitField()); // so addTorrent won't recheck + _manager.saveTorrentStatus(info, s.getBitField(), null); // so addTorrent won't recheck // DirMonitor could grab this first, maybe hold _snarks lock? FileOutputStream out = new FileOutputStream(torrentFile); out.write(info.getTorrentData()); @@ -564,6 +565,8 @@ public class I2PSnarkServlet extends Default { _manager.stopTorrent(snark.torrent, false); } if (_manager.util().connected()) { + // Give the stopped announces time to get out + try { Thread.sleep(2000); } catch (InterruptedException ie) {} _manager.util().disconnect(); _manager.addMessage(_("I2P tunnel closed.")); } @@ -718,7 +721,7 @@ public class I2PSnarkServlet extends Default { curPeers + "/" + ngettext("1 peer", "{0} peers", knownPeers) + ""; else - statusString = "" + _("Not Seeding"); + statusString = "" + _("Complete"); } else { if (isRunning && curPeers > 0 && downBps > 0 && !showPeers) statusString = "" + @@ -727,7 +730,7 @@ public class I2PSnarkServlet extends Default { ngettext("1 peer", "{0} peers", knownPeers) + ""; else if (isRunning && curPeers > 0 && downBps > 0) statusString = "" + - " (" + curPeers + "/" + + curPeers + "/" + ngettext("1 peer", "{0} peers", knownPeers); else if (isRunning && curPeers > 0 && !showPeers) statusString = "" + @@ -790,7 +793,7 @@ public class I2PSnarkServlet extends Default { baseURL = baseURL.substring(e + 1); out.write(" "); + out.write("\" title=\"" + name + ' ' + _("Tracker") + "\" target=\"_blank\">"); out.write(""); out.write(""); break; @@ -804,7 +807,7 @@ public class I2PSnarkServlet extends Default { out.write("\n\t"); out.write(""); if (remaining > 0) - out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB + out.write(formatSize(total-remaining) + " / " + formatSize(total)); // 18MB/3GB; thin space so it will line break well else out.write(formatSize(total)); // 3GB out.write("\n\t"); @@ -828,7 +831,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Stop the torrent")); out.write("\">"); out.write("\"");"); @@ -840,7 +843,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Start the torrent")); out.write("\">"); out.write("\"");"); @@ -856,7 +859,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename)); out.write("')) { return false; }\">"); out.write("\"");"); @@ -871,7 +874,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?", fullFilename)); out.write("')) { return false; }\">"); out.write("\"");"); @@ -1007,7 +1010,7 @@ public class I2PSnarkServlet extends Default { out.write(_("Add torrent")); out.write("\" name=\"foo\" >
\n"); out.write(" "); - out.write(_("You can also copy .torrent files to: {0}.", "" + _manager.getDataDir().getAbsolutePath ())) + ""; + out.write(_("You can also copy .torrent files to: {0}.", "" + _manager.getDataDir().getAbsolutePath () + "")); out.write("\n"); out.write(_("Removing a .torrent will cause it to stop.")); out.write("
\n"); @@ -1262,19 +1265,21 @@ public class I2PSnarkServlet extends Default { // rounding makes us look faster :) private static String formatSize(long bytes) { if (bytes < 5*1024) - return bytes + " B"; + return bytes + " B"; else if (bytes < 5*1024*1024) - return ((bytes + 512)/1024) + " KB"; + return ((bytes + 512)/1024) + " KB"; else if (bytes < 10*1024*1024*1024l) - return ((bytes + 512*1024)/(1024*1024)) + " MB"; + return ((bytes + 512*1024)/(1024*1024)) + " MB"; else - return ((bytes + 512*1024*1024)/(1024*1024*1024)) + " GB"; + return ((bytes + 512*1024*1024)/(1024*1024*1024)) + " GB"; } /** @since 0.7.14 */ private static String urlify(String s) { StringBuilder buf = new StringBuilder(256); - buf.append("").append(s).append(""); + // browsers seem to work without doing this but let's be strict + String link = s.replace("&", "&"); + buf.append("").append(link).append(""); return buf.toString(); } @@ -1313,10 +1318,11 @@ public class I2PSnarkServlet extends Default { * @param r The Resource * @param base The base URL * @param parent True if the parent directory should be included + * @param postParams map of POST parameters or null if not a POST * @return String of HTML * @since 0.7.14 */ - private String getListHTML(Resource r, String base, boolean parent) + private String getListHTML(Resource r, String base, boolean parent, Map postParams) throws IOException { if (!r.isDirectory()) @@ -1341,19 +1347,40 @@ public class I2PSnarkServlet extends Default { else torrentName = title; Snark snark = _manager.getTorrentByBaseName(torrentName); + + if (snark != null && postParams != null) + savePriorities(snark, postParams); + if (title.endsWith("/")) title = title.substring(0, title.length() - 1); title = _("Torrent") + ": " + title; buf.append(title); buf.append("").append(HEADER).append("\n
I2PSnark").append("
"); - - buf.append("
" + - "" + + + if (parent) + { + buf.append("\n
") + .append(_("Up to higher level directory")).append("\n"); + } + + buf.append("
"); + boolean showPriority = snark != null && !snark.storage.complete(); + if (showPriority) + buf.append("
\n"); + buf.append("
" + ""); + buf.append(""); + if (showPriority) + buf.append(""); + buf.append("\n"); //DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, // DateFormat.MEDIUM); + boolean showSaveButton = false; for (int i=0 ; i< ls.length ; i++) { String encoded=URI.encodePath(ls[i]); @@ -1391,7 +1418,8 @@ public class I2PSnarkServlet extends Default { complete = true; status = toImg("tick") + _("Complete"); } else { - status = toImg("clock") + + status = + (snark.storage.getPriority(f.getCanonicalPath()) < 0 ? toImg("cancel") : toImg("clock")) + (100 * (length - remaining) / length) + "% " + _("complete") + " (" + DataHelper.formatSize2(remaining) + _("bytes remaining") + ")"; } @@ -1435,22 +1463,41 @@ public class I2PSnarkServlet extends Default { buf.append("\n"); + buf.append(""); + if (showPriority) { + buf.append(""); + } + buf.append("\n"); } - - if (parent) - { - buf.append("\n"); + if (showSaveButton) { + buf.append("\n"); } - - buf.append("
").append("\"").append(_("File")).append("\" ").append(title).append("").append("\"").append(_("FileSize")).append("\"").append(_("Size")); - buf.append("").append("").append(_("Status")).append("
").append("").append(_("Status")).append("").append(_("Priority")).append("
"); //buf.append(dfmt.format(new Date(item.lastModified()))); buf.append(status); - buf.append("
"); + File f = item.getFile(); + if ((!complete) && (!item.isDirectory()) && f != null) { + int pri = snark.storage.getPriority(f.getCanonicalPath()); + buf.append(" 0) + buf.append("checked=\"true\""); + buf.append('>').append(_("High")); + + buf.append("').append(_("Normal")); + + buf.append("').append(_("Do not download")); + showSaveButton = true; + } + buf.append("
") - .append(_("Up to higher level directory")).append("
 
\n"); -buf.append("
\n"); + if (showPriority) + buf.append(""); + buf.append("\n"); return buf.toString(); } @@ -1515,6 +1562,26 @@ buf.append("\n"); return "\"\" "; } + /** @since 0.8.1 */ + private void savePriorities(Snark snark, Map postParams) { + Set entries = postParams.entrySet(); + for (Map.Entry entry : entries) { + String key = (String)entry.getKey(); + if (key.startsWith("pri.")) { + try { + String file = key.substring(4); + String val = ((String[])entry.getValue())[0]; // jetty arrays + int pri = Integer.parseInt(val); + snark.storage.setPriority(file, pri); + //System.err.println("Priority now " + pri + " for " + file); + } catch (Throwable t) { t.printStackTrace(); } + } + } + if (snark.coordinator != null) + snark.coordinator.updatePiecePriorities(); + _manager.saveTorrentStatus(snark.storage.getMetaInfo(), snark.storage.getBitField(), snark.storage.getFilePriorities()); + } + /** inner class, don't bother reindenting */ private static class FetchAndAdd implements Runnable { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index f6c76d7f60..b93b67490b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -175,7 +175,7 @@ public class EditBean extends IndexBean { } public String getAccessList(int tunnel) { - return getProperty(tunnel, "i2cp.accessList", "").replaceAll(",", "\n"); + return getProperty(tunnel, "i2cp.accessList", "").replace(",", "\n"); } public boolean getClose(int tunnel) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index d91a7900d7..8559c28148 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -666,7 +666,7 @@ public class IndexBean { } public void setAccessList(String val) { if (val != null) - _otherOptions.put("i2cp.accessList", val.trim().replaceAll("\r\n", ",").replaceAll("\n", ",").replaceAll(" ", ",")); + _otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ",")); } public void setCloseTime(String val) { if (val != null) { diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 2e6d09e43d..485c6758c3 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -159,7 +159,7 @@ %> - (<%=intl._("name or destination")%>) + (<%=intl._("name or destination")%>; <%=intl._("b32 not recommended")%>) <% } %> <% if (!"streamrclient".equals(tunnelType)) { %> diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java index 8aac29cffa..834c52160c 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java @@ -45,6 +45,10 @@ public class I2PSocketEepGet extends EepGet { /** this replaces _proxy in the superclass. Sadly, I2PSocket does not extend Socket. */ private I2PSocket _socket; + /** from ConnectionOptions */ + private static final String PROP_CONNECT_DELAY = "i2p.streaming.connectDelay"; + private static final String CONNECT_DELAY = "500"; + public I2PSocketEepGet(I2PAppContext ctx, I2PSocketManager mgr, int numRetries, String outputFile, String url) { this(ctx, mgr, numRetries, -1, -1, outputFile, null, url); } @@ -123,6 +127,10 @@ public class I2PSocketEepGet extends EepGet { Properties props = new Properties(); props.setProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT, "" + CONNECT_TIMEOUT); props.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, "" + INACTIVITY_TIMEOUT); + // This is important - even if the underlying socket doesn't have a connect delay, + // we want to set it for this connection, so the request headers will go out + // in the SYN packet, saving one RTT. + props.setProperty(PROP_CONNECT_DELAY, CONNECT_DELAY); I2PSocketOptions opts = _socketManager.buildOptions(props); _socket = _socketManager.connect(dest, opts); } else { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java index ce41b1e12e..7bd4e45462 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java @@ -24,7 +24,10 @@ public class ConfigLoggingHelper extends HelperBase { public String getMaxFileSize() { int bytes = _context.logManager().getFileSize(); if (bytes <= 0) return "1.00 MB"; - return DataHelper.formatSize2(bytes) + 'B'; + // " " comes back in the POST as 0xc2 0xa0 + // non-breaking space is U+00A0 which is 0xc2 0xa0 in UTF-8. + // we could figure out where the UTF-8 problem is but why bother. + return DataHelper.formatSize2(bytes).replace(" ", " ") + 'B'; } public String getLogLevelTable() { StringBuilder buf = new StringBuilder(32*1024); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index b407fe4948..d20524bfb5 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -142,7 +142,7 @@ public class ConfigUpdateHandler extends FormHandler { } if ( (_updateURL != null) && (_updateURL.length() > 0) ) { - _updateURL = _updateURL.replaceAll("\r\n", ",").replaceAll("\n", ","); + _updateURL = _updateURL.replace("\r\n", ",").replace("\n", ","); String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL); if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) { _context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL); @@ -151,7 +151,7 @@ public class ConfigUpdateHandler extends FormHandler { } if ( (_trustedKeys != null) && (_trustedKeys.length() > 0) ) { - _trustedKeys = _trustedKeys.replaceAll("\r\n", ",").replaceAll("\n", ","); + _trustedKeys = _trustedKeys.replace("\r\n", ",").replace("\n", ","); String oldKeys = new TrustedUpdate(_context).getTrustedKeysString(); if ( (oldKeys == null) || (!_trustedKeys.equals(oldKeys)) ) { _context.router().setConfigSetting(PROP_TRUSTED_KEYS, _trustedKeys); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java index c9b6b7f43f..520c13104e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java @@ -40,7 +40,7 @@ public class ConfigUpdateHelper extends HelperBase { public String getUpdateURL() { String url = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL); if (url != null) - return url.replaceAll(",", "\n"); + return url.replace(",", "\n"); else return ConfigUpdateHandler.DEFAULT_UPDATE_URL; } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java index f499b2ea43..f010be834d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java @@ -31,9 +31,9 @@ public class LogsHelper extends HelperBase { } String str = FileUtil.readTextFile(f.getAbsolutePath(), 250, false); if (str == null) - return ""; + return _("File not found") + ": " + f.getAbsolutePath() + ""; else { - str = str.replaceAll("<", "<").replaceAll(">", ">"); + str = str.replace("&", "&").replace("<", "<").replace(">", ">"); return _("File location") + ": " + f.getAbsolutePath() + "
" + str + "
"; } } @@ -54,12 +54,14 @@ public class LogsHelper extends HelperBase { buf.append("\n"); for (int i = msgs.size(); i > 0; i--) { String msg = msgs.get(i - 1); + msg = msg.replace("&", "&").replace("<", "<").replace(">", ">"); buf.append("
  • "); if (colorize) { String color; // Homeland Security Advisory System // http://www.dhs.gov/xinfoshare/programs/Copy_of_press_release_0046.shtm // but pink instead of yellow for WARN + // FIXME doesnt work for translated levels if (msg.contains("CRIT")) color = "#cc0000"; else if (msg.contains("ERROR")) @@ -71,7 +73,7 @@ public class LogsHelper extends HelperBase { else color = "#006600"; buf.append(""); - buf.append(msg.replaceAll("<", "<").replaceAll(">", ">")); + buf.append(msg); buf.append(""); } else { buf.append(msg); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java index 9206340cab..e9d7d1da68 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java @@ -112,7 +112,7 @@ public class TunnelRenderer { if (lifetime > 10*60) lifetime = 10*60; int bps = 1024 * (int) cfg.getProcessedMessagesCount() / lifetime; - out.write("" + bps + "Bps"); + out.write("" + bps + " Bps"); if (cfg.getSendTo() == null) out.write("" + _("Outbound Endpoint") + ""); else if (cfg.getReceiveFrom() == null) diff --git a/apps/routerconsole/locale/messages_de.po b/apps/routerconsole/locale/messages_de.po index 40ebdebd6b..f58bb8e0e2 100644 --- a/apps/routerconsole/locale/messages_de.po +++ b/apps/routerconsole/locale/messages_de.po @@ -15,6 +15,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "X-Poedit-Language: German\n" #: ../../../router/java/src/net/i2p/router/Blocklist.java:126 diff --git a/apps/routerconsole/locale/messages_fr.po b/apps/routerconsole/locale/messages_fr.po index 123a9adb81..e550c8b652 100644 --- a/apps/routerconsole/locale/messages_fr.po +++ b/apps/routerconsole/locale/messages_fr.po @@ -15,8 +15,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: German\n" -"X-Poedit-Basepath: /home/lee/work/i2p/monotone/i2p.i2p/apps/routerconsole/java\n" +"X-Poedit-Language: French\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" #: ../jsp/WEB-INF/classes/net/i2p/router/web/jsp/config_jsp.java:106 msgid "config networking" diff --git a/apps/routerconsole/locale/messages_nl.po b/apps/routerconsole/locale/messages_nl.po index ffaa50d17f..52aaf08d8e 100644 --- a/apps/routerconsole/locale/messages_nl.po +++ b/apps/routerconsole/locale/messages_nl.po @@ -15,6 +15,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "X-Poedit-Language: Dutch\n" #: ../../../router/java/src/net/i2p/router/Blocklist.java:126 diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java index c6596b7c93..72eb38e606 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java @@ -47,6 +47,9 @@ public class ConnectionOptions extends I2PSocketOptionsImpl { private int _maxTotalConnsPerHour; private int _maxTotalConnsPerDay; + // NOTE - almost all the options are below, but see + // I2PSocketOptions in ministreaming for a few more + public static final int PROFILE_BULK = 1; public static final int PROFILE_INTERACTIVE = 2; diff --git a/apps/systray/doc/README.txt b/apps/systray/doc/README.txt new file mode 100644 index 0000000000..3123d533fc --- /dev/null +++ b/apps/systray/doc/README.txt @@ -0,0 +1,21 @@ +Bundled in ../java/lib/ are the binaries for systray4j version 2.4.1 2004-03-28, +which is still the latest. + +Files are from systray4j-2.4.1-win32.zip. + +SHA1Sums: + 28acaea97816f53d188d01fd88b72e670e67286b systray4j-2.4.1-win32.zip + a7f5e02c3652f3f1a72559e54ee69226b8b97859 systray4j.dll + 947bd91c483494256cf48ad87c211e8701b4f85b systray4j.jar + + +systray4j is GPLv2, see LICENSE.systray4j.txt. +I2P systray code in ../java/src is public domain. + +SysTray is really obsolete. It supports Windows and kde3 only. +We only instantiate it on Windows. + +The java.awt.SystemTray classes added in Java 6 +(and used by apps/desktopgui) are the way to go now. + +We could either rewrite this to use SystemTray, or switch to desktopgui. diff --git a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java index f7e6499421..23a11ef231 100644 --- a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java +++ b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java @@ -184,4 +184,20 @@ public class SysTray implements SysTrayMenuListener { _sysTrayMenu.addItem(_itemOpenConsole); refreshDisplay(); } + + /** + * Starts SysTray, even on linux (but requires kde3 libsystray4j.so to do anything) + * @since 0.8.1 + */ + public static void main(String args[]) { + System.err.println("SysTray4j version " + SysTrayMenu.VERSION); + System.err.println("Hit ^C to exit"); + new SysTray(); + Thread t = Thread.currentThread(); + synchronized(t) { + try { + t.wait(); + } catch (InterruptedException ie) {} + } + } } diff --git a/build.xml b/build.xml index 88a3caa8c9..af467498b8 100644 --- a/build.xml +++ b/build.xml @@ -6,6 +6,14 @@ + + diff --git a/core/java/build.xml b/core/java/build.xml index a52dcb6354..2ae9d655fa 100644 --- a/core/java/build.xml +++ b/core/java/build.xml @@ -17,10 +17,11 @@ + - + diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index bb1ffe55c0..c0fa56e3a8 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -1076,7 +1076,8 @@ public class DataHelper { } /** - * Like formatSize but with a space after the number + * Like formatSize but with a non-breaking space after the number + * Use only in HTML * @since 0.7.14 */ public static String formatSize2(long bytes) { @@ -1091,11 +1092,11 @@ public class DataHelper { String str = fmt.format(val); switch (scale) { - case 1: return str + " K"; - case 2: return str + " M"; - case 3: return str + " G"; - case 4: return str + " T"; - default: return bytes + " "; + case 1: return str + " K"; + case 2: return str + " M"; + case 3: return str + " G"; + case 4: return str + " T"; + default: return bytes + " "; } } diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java index ec759e0f28..b56f197ab9 100644 --- a/core/java/src/net/i2p/util/FileUtil.java +++ b/core/java/src/net/i2p/util/FileUtil.java @@ -13,10 +13,18 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarOutputStream; -import java.util.jar.Pack200; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +// Pack200 import +// you must also uncomment the correct line in unpack() below +// For gcj, gij, etc., comment both out +// +// For Sun, OpenJDK, IcedTea, etc, use this +import java.util.jar.Pack200; + +// For Apache Harmony or if you put its pack200.jar in your library directory use this +//import org.apache.harmony.unpack200.Archive; /** * General helper methods for messing with files @@ -119,7 +127,7 @@ public class FileUtil { if (entry.getName().endsWith(".jar.pack") || entry.getName().endsWith(".war.pack")) { target = new File(targetDir, entry.getName().substring(0, entry.getName().length() - ".pack".length())); JarOutputStream fos = new JarOutputStream(new FileOutputStream(target)); - Pack200.newUnpacker().unpack(in, fos); + unpack(in, fos); fos.close(); System.err.println("INFO: File [" + entry.getName() + "] extracted and unpacked"); } else { @@ -189,9 +197,7 @@ public class FileUtil { } else { if (p200TestRequired && (entry.getName().endsWith(".jar.pack") || entry.getName().endsWith(".war.pack"))) { - try { - Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader()); - } catch (Exception e) { // ClassNotFoundException but compiler not happy with that + if (!isPack200Supported()) { System.err.println("ERROR: Zip verify failed, your JVM does not support unpack200"); return false; } @@ -224,6 +230,40 @@ public class FileUtil { } } + /** + * This won't work right if one of the two options in unpack() is commented out. + * @since 0.8.1 + */ + private static boolean isPack200Supported() { + try { + Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader()); + return true; + } catch (Exception e) {} + try { + Class.forName("org.apache.harmony.pack200.Archive", false, ClassLoader.getSystemClassLoader()); + return true; + } catch (Exception e) {} + return false; + } + + /** + * Caller must close streams + * @since 0.8.1 + */ + private static void unpack(InputStream in, JarOutputStream out) throws Exception { + // For Sun, OpenJDK, IcedTea, etc, use this + Pack200.newUnpacker().unpack(in, out); + + // ------------------ + // For Apache Harmony or if you put its pack200.jar in your library directory use this + //(new Archive(in, out)).unpack(); + + + // ------------------ + // For gcj, gij, etc., use this + //throw new IOException("Pack200 not supported"); + } + /** * Read in the last few lines of a (newline delimited) textfile, or null if * the file doesn't exist. @@ -352,6 +392,18 @@ public class FileUtil { boolean copied = FileUtil.copy(args[1], args[2], false); if (!copied) System.err.println("Error copying [" + args[1] + "] to [" + args[2] + "]"); + } else if ("unzip".equals(args[0])) { + File f = new File(args[1]); + File to = new File("tmp"); + to.mkdir(); + boolean copied = verifyZip(f); + if (!copied) + System.err.println("Error verifying " + args[1]); + copied = extractZip(f, to); + if (copied) + System.err.println("Unzipped [" + args[1] + "] to [" + to + "]"); + else + System.err.println("Error unzipping [" + args[1] + "] to [" + to + "]"); } } diff --git a/core/java/src/net/i2p/util/LogManager.java b/core/java/src/net/i2p/util/LogManager.java index d3ece13c56..634b4c1ade 100644 --- a/core/java/src/net/i2p/util/LogManager.java +++ b/core/java/src/net/i2p/util/LogManager.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; @@ -429,7 +430,8 @@ public class LogManager { v = v.substring(0, v.length() - 1); char mod = v.charAt(v.length() - 1); if (!Character.isDigit(mod)) v = v.substring(0, v.length() - 1); - double val = Double.parseDouble(v); + // output to form was in current locale, so have to parse it back that way + double val = (new DecimalFormat()).parse(v.trim()).doubleValue(); switch (mod) { case 'K': val *= 1024; diff --git a/core/java/src/net/i2p/util/SecureFileOutputStream.java b/core/java/src/net/i2p/util/SecureFileOutputStream.java index 8a891344b1..3df1f14a58 100644 --- a/core/java/src/net/i2p/util/SecureFileOutputStream.java +++ b/core/java/src/net/i2p/util/SecureFileOutputStream.java @@ -56,7 +56,7 @@ public class SecureFileOutputStream extends FileOutputStream { * Tries to set the permissions to 600, * ignores errors */ - private static void setPerms(File f) { + public static void setPerms(File f) { if (!canSetPerms) return; try { diff --git a/history.txt b/history.txt index 90664730b1..aff467135c 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,157 @@ +2010-11-03 zzz + * Merge and snark fixups + +2010-11-01 zzz + * ClientConnectionRunner: Add synch to fix race causing AIOOBE + (http://forum.i2p/viewtopic.php?t=5061) + * configlogging.jsp: Parse log limit with current locale + (ticket #118) + * i2psnark: + - Limit number of parallel requests of a single piece when in the end game + - Shorten and weight the speed tracker so the display is more + reflective of current speed + * logs.jsp: Add message if wrapper log not found + (ticket #103) + +2010-10-30 zzz + * i2psnark: + - Priority mapping bugfix + - Close files as we go when creating/checking + so we don't run out of file descriptors + - Update request queues after priority change + - Only add wanted pieces to wanted list at startup + - Make sure lastRequest is null when it should be + - Delay during StopAll so we don't close the tunnel before the + stopped announces go out and reopen it + - Logging tweaks + +2010-10-27 zzz + * i2psnark: + - Don't stay interested if we run out of pieces + to request (thanks sponge) + - Enhance debug mode to show requests + - Priority mapping bugfix + * Transport: Avoid rare NPE at startup + +2010-10-24 zzz + * FileUtil: Make it easier to compile without Pack200, or with + Apache Harmony's Pack200, add unzip to main() + * i2psnark: Catch a race after disconnect() + * NTCP: Catch a race after stop() + * Router: Set permissions on wrapper.log when not called by RouterLaunch + * Systray: New doc and main() + +2010-10-19 zzz + * Escape & in logs and i2psnark (much more to do) + * JobImpl: Deprecate two debugging methods + * replaceAll() -> replace() when we don't need regex + +2010-10-15 zzz + * i2psnark: Add file priority feature + * I2PSocketEepGet: Set connect delay to save a RTT, will + speed announces in i2psnark + +2010-10-12 zzz + *** 1.6 or higher JDK now required to build + * configlogging.jsp: + - Add easy way to add an override + - Make file size specifier more flexible + * Console: + - Sort RouterAddress options on netdb.jsp and peers.jsp + - Remove unused web-*.xml file from war + * Crypto: + - Convert all ArrayList caching to LBQs in YKGenerator, + HMACGenerator, and AESKeyCache. + - Change DSAEngine params from Hash to new SHA1Hash, since + these were really 20 byte hashes, not 32 byte Hashes. + - Add stats to track YKGenerator caching success + - Fix YKGenerator precalculation to be much more useful by + increasing the cache size and dramatically shortening the delay + - Option cleanups + - YKGenerator cleanups + - Mark HMAC256Generator unused + * EepGet: Reset length variable on redirect + * Files: Change permissions to 600/700 for all written files/directories. + Now requires Java 1.6 to build, but only 1.5+ to run. + (requires 1.6 to set permissiomns) + * GeoIP: Fix locking bug causing lookups to stop + * Hash: Throw IAE if data length is not 32 bytes, + now that DSAEngine abuse is gone + * HTTPResponseOutputStream: + - More caching + - Stats cleanup + - Max header length check + - Catch OOM + - Initializer cleanup + - Javadoc + * I2CP: + - Add new option i2cp.messageReliability=none, which prevents the + router from sending MessageStatusMessages back in reply to an + outbound SendMessageMessage. Since the streaming lib always ignored + the MSMs anyway, make it the default for streaming. + This will reduce the I2CP traffic significantly. + MSM handling now avoided, but it is still fairly broken, see + comments in I2PSessionImpl2. + - Cleanups to replace method calls with fields + - More cleanups, javadoc, rate reduction + * i2psnark: + - Compact response format + - Add link to finished torrent in message box + - Don't let one bad torrent prevent others from + starting or stopping + - Sort peers by completion % + - Add some missing mime types to web.xml + - shouldLog() cleanup + * i2ptunnel: + - Now that streaming flush() is fixed, use it in IRCClient, and + for initial data in I2PTunnel runner, to avoid the 250 ms + passive flush delay + - Add hostname DSA signature field, to be used for addkey forms. + Experimental, may be commented out later. + - More header blocking (thanks telecomix!) + - Remove unused web-*.xml file from war + * Installer: Add startup hint for non-x86 + * Javadoc updates all over the place + * LogConsoleBuffer: Java 5 + * Naming: + - Increase cache size and expiration time + - Add clearCache() method + - Don't use EepGet or Exec for b32 + - Javadoc updates + * NetDB: + - Expire unreachable routers quickly, even if they don't have introducers, + so we don't have old data on routers that ran out of introducers. + - Fix rare NPEs at shutdown + * NTCP: + - Cleanups + * Streaming: + - Make flush() block less, by waiting only for "accept" into the + streaming queue rather than "completion" (i.e. ACK from the far end). + This prevents complete stalls when flushing, and should help performance + of apps that use flush(), like i2psnark (and SAM?). + close() still does a flush that waits for completion, as i2ptunnel + doesn't like a fast return from close(). + - cleanups + * SusiDNS: + - Remove unused web-*.xml file from war + * TransportManager: Convert _transports from a List to a CHM + to prevent a rare concurrent exception + * Tunnels: + - Don't use peers < 0.7.9 for tunnels due to the old + message corruption bugs + - Javadoc + - Cleanups + * UDP: + - Beginnings of destroy message support + - Try to avoid running out of introducers by relaxing selection criteria + and increasing minimum number of potential introducers + - Avoid rare AIOOBE + - PacketBuilder refactor + - Make most classes package private + - Comments + - Logging cleanup + - Comment out a main() + 2010-10-22 sponge * Sanity and some fixs for slackware package diff --git a/router/java/src/net/i2p/router/JobImpl.java b/router/java/src/net/i2p/router/JobImpl.java index b122d3674a..2f99a53571 100644 --- a/router/java/src/net/i2p/router/JobImpl.java +++ b/router/java/src/net/i2p/router/JobImpl.java @@ -39,11 +39,21 @@ public abstract class JobImpl implements Job { return buf.toString(); } + /** + * @deprecated + * As of 0.8.1, this is a noop, as it just adds classes to the log manager + * class list for no good reason. Logging in jobs is almost always + * set explicitly rather than by class name. + */ void addedToQueue() { - if (_context.logManager().getLog(getClass()).shouldLog(Log.DEBUG)) - _addedBy = new Exception(); + //if (_context.logManager().getLog(getClass()).shouldLog(Log.DEBUG)) + // _addedBy = new Exception(); } + /** + * @deprecated + * @return null always + */ public Exception getAddedBy() { return _addedBy; } public long getMadeReadyOn() { return _madeReadyOn; } public void madeReady() { _madeReadyOn = _context.clock().now(); } diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index 892a678edc..a8b5395b00 100644 --- a/router/java/src/net/i2p/router/JobQueue.java +++ b/router/java/src/net/i2p/router/JobQueue.java @@ -142,8 +142,9 @@ public class JobQueue { public void addJob(Job job) { if (job == null || !_alive) return; - if (job instanceof JobImpl) - ((JobImpl)job).addedToQueue(); + // This does nothing + //if (job instanceof JobImpl) + // ((JobImpl)job).addedToQueue(); long numReady = 0; boolean alreadyExists = false; diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index a091cba134..bfcbe83a6f 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -218,6 +218,19 @@ public class Router { // NOW we start all the activity _context.initAll(); + // Set wrapper.log permissions. + // Just hope this is the right location, we don't know for sure, + // but this is the same method used in LogsHelper and we have no complaints. + // (we could look for the wrapper.config file and parse it I guess...) + // If we don't have a wrapper, RouterLaunch does this for us. + if (System.getProperty("wrapper.version") != null) { + File f = new File(System.getProperty("java.io.tmpdir"), "wrapper.log"); + if (!f.exists()) + f = new File(_context.getBaseDir(), "wrapper.log"); + if (f.exists()) + SecureFileOutputStream.setPerms(f); + } + _routerInfo = null; _higherVersionSeen = false; _log = _context.logManager().getLog(Router.class); diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index df69e964f9..f2522cd4ed 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 05; + public final static long BUILD = 9; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index f363bd2781..1c4fb2fd5e 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -361,18 +361,21 @@ public class ClientConnectionRunner { // TunnelPool.locked_buildNewLeaseSet() ensures that leases are sorted, // so the comparison will always work. int leases = set.getLeaseCount(); - if (_currentLeaseSet != null && _currentLeaseSet.getLeaseCount() == leases) { - for (int i = 0; i < leases; i++) { - if (! _currentLeaseSet.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId())) - break; - if (! _currentLeaseSet.getLease(i).getGateway().equals(set.getLease(i).getGateway())) - break; - if (i == leases - 1) { - if (_log.shouldLog(Log.INFO)) - _log.info("Requested leaseSet hasn't changed"); - if (onCreateJob != null) - _context.jobQueue().addJob(onCreateJob); - return; // no change + // synch so _currentLeaseSet isn't changed out from under us + synchronized (this) { + if (_currentLeaseSet != null && _currentLeaseSet.getLeaseCount() == leases) { + for (int i = 0; i < leases; i++) { + if (! _currentLeaseSet.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId())) + break; + if (! _currentLeaseSet.getLease(i).getGateway().equals(set.getLease(i).getGateway())) + break; + if (i == leases - 1) { + if (_log.shouldLog(Log.INFO)) + _log.info("Requested leaseSet hasn't changed"); + if (onCreateJob != null) + _context.jobQueue().addJob(onCreateJob); + return; // no change + } } } } @@ -590,7 +593,7 @@ public class ClientConnectionRunner { + " for session [" + _sessionId.getSessionId() + "] (with nonce=2), retrying after [" + (_context.clock().now() - _lastTried) - + "]", getAddedBy()); + + "]"); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Updating message status for message " + _messageId + " to " diff --git a/router/java/src/net/i2p/router/message/SendMessageDirectJob.java b/router/java/src/net/i2p/router/message/SendMessageDirectJob.java index be1b1e3eb2..c67eeaa4ad 100644 --- a/router/java/src/net/i2p/router/message/SendMessageDirectJob.java +++ b/router/java/src/net/i2p/router/message/SendMessageDirectJob.java @@ -76,7 +76,7 @@ public class SendMessageDirectJob extends JobImpl { if (_expiration < now) { if (_log.shouldLog(Log.WARN)) _log.warn("Timed out sending message " + _message + " directly (expiration = " - + new Date(_expiration) + ") to " + _targetHash.toBase64(), getAddedBy()); + + new Date(_expiration) + ") to " + _targetHash.toBase64()); if (_onFail != null) getContext().jobQueue().addJob(_onFail); return; @@ -104,7 +104,7 @@ public class SendMessageDirectJob extends JobImpl { if (_log.shouldLog(Log.WARN)) _log.warn("Unable to find the router to send to: " + _targetHash + " after searching for " + (getContext().clock().now()-_searchOn) - + "ms, message: " + _message, getAddedBy()); + + "ms, message: " + _message); if (_onFail != null) getContext().jobQueue().addJob(_onFail); } diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 196876383e..39856f509f 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -136,17 +136,17 @@ public class CommSystemFacadeImpl extends CommSystemFacade { @Override public boolean isBacklogged(Hash dest) { - return _manager.isBacklogged(dest); + return _manager != null && _manager.isBacklogged(dest); } @Override public boolean isEstablished(Hash dest) { - return _manager.isEstablished(dest); + return _manager != null && _manager.isEstablished(dest); } @Override public boolean wasUnreachable(Hash dest) { - return _manager.wasUnreachable(dest); + return _manager != null && _manager.wasUnreachable(dest); } @Override diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 52c0218fc7..58ebe36347 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -615,8 +615,8 @@ public class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { if (_log.shouldLog(Log.INFO)) _log.info("Type " + msg.getMessage().getType() + " pri " + msg.getPriority() + " slot " + slot); boolean removed = _outbound.remove(msg); - if ((!removed) && _log.shouldLog(Log.ERROR)) - _log.info("Already removed??? " + msg.getMessage().getType()); + if ((!removed) && _log.shouldLog(Log.WARN)) + _log.warn("Already removed??? " + msg.getMessage().getType()); } _currentOutbound = msg; } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java index 207db4c3ca..fd5cf1ac91 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPSendFinisher.java @@ -2,6 +2,7 @@ package net.i2p.router.transport.ntcp; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadFactory; @@ -24,9 +25,9 @@ import net.i2p.util.Log; */ public class NTCPSendFinisher { private static final int THREADS = 4; - private I2PAppContext _context; - private NTCPTransport _transport; - private Log _log; + private final I2PAppContext _context; + private final NTCPTransport _transport; + private final Log _log; private int _count; private ThreadPoolExecutor _executor; @@ -47,7 +48,12 @@ public class NTCPSendFinisher { } public void add(OutNetMessage msg) { - _executor.execute(new RunnableEvent(msg)); + try { + _executor.execute(new RunnableEvent(msg)); + } catch (RejectedExecutionException ree) { + // race with stop() + _log.warn("NTCP send finisher stopped, discarding msg.afterSend()"); + } } // not really needed for now but in case we want to add some hooks like afterExecute() diff --git a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java index ba3eda6d66..74910cbcbb 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java @@ -317,7 +317,7 @@ class TestJob extends JobImpl { public String getName() { return "Tunnel test timeout"; } public void runJob() { if (_log.shouldLog(Log.WARN)) - _log.warn("Timeout: found? " + _found, getAddedBy()); + _log.warn("Timeout: found? " + _found); if (!_found) { // don't clog up the SKM with old one-tag tagsets if (_cfg.isInbound() && !_pool.getSettings().isExploratory()) { diff --git a/router/java/test/net/i2p/router/message/SendGarlicJob.java b/router/java/test/net/i2p/router/message/SendGarlicJob.java index 6b0aa0b375..f4839a522d 100644 --- a/router/java/test/net/i2p/router/message/SendGarlicJob.java +++ b/router/java/test/net/i2p/router/message/SendGarlicJob.java @@ -81,7 +81,7 @@ public class SendGarlicJob extends JobImpl { long after = getContext().clock().now(); if ( (after - before) > 1000) { if (_log.shouldLog(Log.WARN)) - _log.warn("Building the garlic took too long [" + (after-before)+" ms]", getAddedBy()); + _log.warn("Building the garlic took too long [" + (after-before)+" ms]"); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Building the garlic was fast! " + (after - before) + " ms");