@Override public void announce(AnnounceRequestMessage.RequestEvent event, boolean inhibitEvents) throws AnnounceException { logger.info( "Announcing{} to tracker with {}U/{}D/{}L bytes...", new Object[] { this.formatAnnounceEvent(event), this.torrent.getUploaded(), this.torrent.getDownloaded(), this.torrent.getLeft() }); State state = State.CONNECT_REQUEST; int maxAttempts = AnnounceRequestMessage.RequestEvent.STOPPED.equals(event) ? UDP_MAX_TRIES_ON_STOPPED : UDP_MAX_TRIES; int attempts = -1; try { this.socket = new DatagramSocket(); this.socket.connect(this.address); while (++attempts <= maxAttempts) { // Transaction ID is randomized for each exchange. this.transactionId = this.random.nextInt(); // Immediately decide if we can send the announce request // directly or not. For this, we need a valid, non-expired // connection ID. if (this.connectionExpiration != null) { if (new Date().before(this.connectionExpiration)) { state = State.ANNOUNCE_REQUEST; } else { logger.debug("Announce connection ID expired, " + "reconnecting with tracker..."); } } switch (state) { case CONNECT_REQUEST: this.send(UDPConnectRequestMessage.craft(this.transactionId).getData()); try { this.handleTrackerConnectResponse( UDPTrackerMessage.UDPTrackerResponseMessage.parse(this.recv(attempts))); attempts = -1; } catch (SocketTimeoutException ste) { // Silently ignore the timeout and retry with a // longer timeout, unless announce stop was // requested in which case we need to exit right // away. if (stop) { return; } } break; case ANNOUNCE_REQUEST: this.send(this.buildAnnounceRequest(event).getData()); try { this.handleTrackerAnnounceResponse( UDPTrackerMessage.UDPTrackerResponseMessage.parse(this.recv(attempts)), inhibitEvents); // If we got here, we succesfully completed this // announce exchange and can simply return to exit the // loop. return; } catch (SocketTimeoutException ste) { // Silently ignore the timeout and retry with a // longer timeout, unless announce stop was // requested in which case we need to exit right // away. if (stop) { return; } } break; default: throw new IllegalStateException("Invalid announce state!"); } } // When the maximum number of attempts was reached, the announce // really timed-out. We'll try again in the next announce loop. throw new AnnounceException( "Timeout while announcing" + this.formatAnnounceEvent(event) + " to tracker!"); } catch (IOException ioe) { throw new AnnounceException( "Error while announcing" + this.formatAnnounceEvent(event) + " to tracker: " + ioe.getMessage(), ioe); } catch (MessageValidationException mve) { throw new AnnounceException( "Tracker message violates expected " + "protocol (" + mve.getMessage() + ")", mve); } }
/** * Build, send and process a tracker announce request. * * <p>This function first builds an announce request for the specified event with all the required * parameters. Then, the request is made to the tracker and the response analyzed. * * <p>All registered {@link AnnounceResponseListener} objects are then fired with the decoded * payload. * * @param event The announce event type (can be AnnounceEvent.NONE for periodic updates). * @param inhibitEvents Prevent event listeners from being notified. */ @Override public void announce(AnnounceRequestMessage.RequestEvent event, boolean inhibitEvents) throws AnnounceException { logger.info( "Announcing{} to tracker with {}U/{}D/{}L bytes...", new Object[] { this.formatAnnounceEvent(event), this.torrent.getUploaded(), this.torrent.getDownloaded(), this.torrent.getLeft() }); URL target = null; try { HTTPAnnounceRequestMessage request = this.buildAnnounceRequest(event); target = request.buildAnnounceURL(this.tracker.toURL()); } catch (MalformedURLException mue) { throw new AnnounceException("Invalid announce URL (" + mue.getMessage() + ")", mue); } catch (MessageValidationException mve) { throw new AnnounceException( "Announce request creation violated " + "expected protocol (" + mve.getMessage() + ")", mve); } catch (IOException ioe) { throw new AnnounceException( "Error building announce request (" + ioe.getMessage() + ")", ioe); } HttpURLConnection conn = null; InputStream in = null; try { conn = (HttpURLConnection) target.openConnection(); in = conn.getInputStream(); } catch (IOException ioe) { if (conn != null) { in = conn.getErrorStream(); } } // At this point if the input stream is null it means we have neither a // response body nor an error stream from the server. No point in going // any further. if (in == null) { throw new AnnounceException("No response or unreachable tracker!"); } try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(in); // Parse and handle the response HTTPTrackerMessage message = HTTPTrackerMessage.parse(ByteBuffer.wrap(baos.toByteArray())); baos.close(); // close stream this.handleTrackerAnnounceResponse(message, inhibitEvents); } catch (IOException ioe) { throw new AnnounceException("Error reading tracker response!", ioe); } catch (MessageValidationException mve) { throw new AnnounceException( "Tracker message violates expected " + "protocol (" + mve.getMessage() + ")", mve); } finally { // Make sure we close everything down at the end to avoid resource // leaks. try { in.close(); } catch (IOException ioe) { logger.warn("Problem ensuring error stream closed!", ioe); } // This means trying to close the error stream as well. InputStream err = conn.getErrorStream(); if (err != null) { try { err.close(); } catch (IOException ioe) { logger.warn("Problem ensuring error stream closed!", ioe); } } } }