/* * Snark - Main snark program startup class. Copyright (C) 2003 Mark J. Wielaard * * This file is part of Snark. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.klomp.snark; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.ServerSocket; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; import java.util.Random; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import org.klomp.snark.bencode.BDecoder; import p2pp.PeerCoordinatorFactory; /** * Main Snark object used to fetch or serve a given file. * * @author Mark Wielaard (mark@klomp.org) */ public class Snark { /** The lowest port Snark will listen on for connections */ public final static int MIN_PORT = 6881; /** The highest port Snark will listen on for connections */ public final static int MAX_PORT = 6889; /** The path to the file being torrented */ public String torrent; /** The metadata known about the torrent */ public MetaInfo meta; /** The storage helper assisting us */ public Storage storage; /** The coordinator managing our peers */ public PeerCoordinator coordinator; /** Parcels out incoming requests to the appropriate places */ public ConnectionAcceptor acceptor; /** Obtains information on new peers. */ public TrackerClient trackerclient; // Factory used to create coordinators public PeerCoordinatorFactory fac; /** * Constructs a Snark client. * @param torrent The address of the torrent to download or file to serve * @param ip The IP address to use when serving data * @param user_port The port number to use * @param slistener A custom {@link StorageListener} to use * @param clistener A custom {@link CoordinatorListener} to use */ public Snark (String torrent, String ip, int user_port, StorageListener slistener, CoordinatorListener clistener) { this.slistener = slistener; this.clistener = clistener; this.torrent = torrent; this.user_port = user_port; this.ip = ip; // Create a new ID and fill it with something random. First nine // zeros bytes, then three bytes filled with snark and then // sixteen random bytes. Random random = new Random(); int i; for (i = 0; i < 9; i++) { id[i] = 0; } id[i++] = snark; id[i++] = snark; id[i++] = snark; while (i < 20) { id[i++] = (byte)random.nextInt(256); } log.log(Level.FINE, "My peer id: " + PeerID.idencode(id)); } /** * Sets the PeerCoordinator [LIMA]. */ public void setPeerCoordinatorFactory(PeerCoordinatorFactory fac) { this.fac = fac; } /** * Sets the global logging level of Snark. */ public static void setLogLevel (Level level) { log.setLevel(level); log.setUseParentHandlers(false); Handler handler = new ConsoleHandler(); handler.setLevel(level); log.addHandler(handler); } /** * Returns a human-readable state of Snark. */ public String getStateString () { return activities[activity]; } /** * Returns the integer code for the human-readable state of Snark. */ public int getState () { return activity; } /** * Establishes basic information such as {@link #id}, opens ports, * and determines whether to act as a peer or seed. */ public void setupNetwork () throws IOException { activity = NETWORK_SETUP; IOException lastException = null; if (user_port != -1) { port = user_port; try { serversocket = new ServerSocket(port); } catch (IOException ioe) { lastException = ioe; } } else { for (port = MIN_PORT; serversocket == null && port <= MAX_PORT; port++) { try { serversocket = new ServerSocket(port); } catch (IOException ioe) { lastException = ioe; } } } if (serversocket == null) { String message = "Cannot accept incoming connections "; if (user_port == -1) { message = message + "tried ports " + MIN_PORT + " - " + MAX_PORT; } else { message = message + "on port " + user_port; } if (ip != null || user_port != -1) { abort(message, lastException); } else { log.log(Level.WARNING, message); } port = -1; } else { port = serversocket.getLocalPort(); log.log(Level.FINE, "Listening on port: " + port); } // Figure out what the torrent argument represents. meta = null; File f = null; try { InputStream in; f = new File(torrent); if (f.exists()) { in = new FileInputStream(f); } else { activity = GETTING_TORRENT; URL u = new URL(torrent); URLConnection c = u.openConnection(); c.connect(); in = c.getInputStream(); if (c instanceof HttpURLConnection) { // Check whether the page exists int code = ((HttpURLConnection)c).getResponseCode(); if (code / 100 != 2) { // responses abort("Loading page '" + torrent + "' gave error code " + code + ", it probably doesn't exists"); } } } meta = new MetaInfo(new BDecoder(in)); } catch (IOException ioe) { // OK, so it wasn't a torrent metainfo file. if (f != null && f.exists()) { if (ip == null) { abort("'" + torrent + "' exists," + " but is not a valid torrent metainfo file." + System.getProperty("line.separator") + " (use --share to create a torrent from it" + " and start sharing)", ioe); } else { // Try to create a new metainfo file log.log(Level.INFO, "Trying to create metainfo torrent for '" + torrent + "'"); try { activity = CREATING_TORRENT; storage = new Storage(f, "http://" + ip + ":" + port + "/announce", slistener); storage.create(); meta = storage.getMetaInfo(); } catch (IOException ioe2) { abort("Could not create torrent for '" + torrent + "'", ioe2); } } } else { abort("Cannot open '" + torrent + "'", ioe); } } log.log(Level.INFO, meta.toString()); } /** * Start the upload/download process and begins exchanging pieces * with other peers. */ public void collectPieces () throws IOException { // When the metainfo torrent was created from an existing file/dir // it already exists. if (storage == null) { try { activity = CHECKING_STORAGE; storage = new Storage(meta, slistener); storage.check(); } catch (IOException ioe) { abort("Could not create storage", ioe); } } activity = COLLECTING_PIECES; if(fac == null) coordinator = new PeerCoordinator(id, meta, storage, clistener); else coordinator = fac.getPeerCoordinator(id, meta, storage, clistener); HttpAcceptor httpacceptor; if (ip != null) { MetaInfo m = meta.reannounce("http://" + ip + ":" + port + "/announce"); Tracker tracker = new Tracker(m); try { tracker.addPeer(meta.getHexInfoHash(), new PeerID(id, InetAddress.getByName(ip), port)); } catch (UnknownHostException oops) { abort("Could not start tracker for " + ip, oops); } httpacceptor = new HttpAcceptor(tracker); // Debug code for writing out .torrent to disk /* byte[] torrentData = tracker.getMetaInfo( meta.getHexInfoHash()).getTorrentData(); try { log.log(Level.INFO, "Writing torrent to file " + torrent + ".torrent"); FileOutputStream fos = new FileOutputStream(torrent + ".torrent"); fos.write(torrentData); fos.close(); } catch (IOException e) { log.log(Level.WARNING, "Could not save torrent file."); } */ } else { httpacceptor = null; } PeerAcceptor peeracceptor = new PeerAcceptor(coordinator); acceptor = new ConnectionAcceptor(serversocket, httpacceptor, peeracceptor); acceptor.start(); if (ip != null) { log.log(Level.INFO, "Torrent available on " + "http://" + ip + ":" + port + "/" + meta.getHexInfoHash() + ".torrent"); } trackerclient = new TrackerClient(meta, coordinator, port); trackerclient.start(); coordinator.setTracker(trackerclient); } /** * Aborts program abnormally. */ public static void abort (String s) throws IOException { abort(s, null); } /** * Aborts program abnormally. */ public static void abort (String s, IOException ioe) throws IOException { log.log(Level.SEVERE, s, ioe); throw new IOException(s); } /** The listen port requested by the user */ protected int user_port; /** The port number Snark listens on */ protected int port; /** The IP address to listen on, if applicable */ protected String ip; /** The {@link StorageListener} to send updates to */ protected StorageListener slistener; /** The {@link CoordinatorListener} to send updates to */ protected CoordinatorListener clistener; /** Our BitTorrent client id number, randomly assigned */ protected byte[] id = new byte[20]; /** The server socket that we are using to listen for connections */ protected ServerSocket serversocket; /** * A magic constant used to identify the Snark library in the clientid. * * <pre>Taking Three as the subject to reason about-- * A convenient number to state-- * We add Seven, and Ten, and then multiply out * By One Thousand diminished by Eight. * * The result we proceed to divide, as you see, * By Nine Hundred and Ninety Two: * Then subtract Seventeen, and the answer must be * Exactly and perfectly true.</pre> */ protected static final byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17; /** An integer indicating Snark's current activity. */ protected int activity = NOT_STARTED; /** The list of possible activities */ protected static final String[] activities = {"Not started", "Network setup", "Getting torrent", "Creating torrent", "Checking storage", "Collecting pieces", "Seeding"}; public static final int NOT_STARTED = 0; public static final int NETWORK_SETUP = 1; public static final int GETTING_TORRENT = 2; public static final int CREATING_TORRENT = 3; public static final int CHECKING_STORAGE = 4; public static final int COLLECTING_PIECES = 5; public static final int SEEDING = 6; /** The Java logger used to process our log events. */ protected static final Logger log = Logger.getLogger("org.klomp.snark"); }