/* * Copyright (c) 2001 Sun Microsystems, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Sun Microsystems, Inc. for Project JXTA." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" * must not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", * nor may "JXTA" appear in their name, without prior written * permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of Project JXTA. For more * information on Project JXTA, please see * <http://www.jxta.org/>. * * This license is based on the BSD license adopted by the Apache Foundation. * * $Id: xfer.java,v 1.28 2007/02/15 20:53:26 bondolo Exp $ */ package net.jxta.impl.shell.bin.xfer; import net.jxta.discovery.DiscoveryService; import net.jxta.document.AdvertisementFactory; import net.jxta.endpoint.EndpointAddress; import net.jxta.id.IDFactory; import net.jxta.impl.shell.GetOpt; import net.jxta.impl.shell.ShellApp; import net.jxta.impl.shell.ShellEnv; import net.jxta.impl.shell.ShellObject; import net.jxta.peergroup.PeerGroup; import net.jxta.pipe.PipeService; import net.jxta.protocol.PipeAdvertisement; import net.jxta.socket.JxtaSocket; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.Socket; import java.net.URISyntaxException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import net.jxta.id.ID; import net.jxta.peer.PeerID; /** * send a file from one peer to another. destination may be an endpoint address * or a pipe. */ public class xfer extends ShellApp { static final String XFERFILEINFO_ELEMENT = "JxtaXfer:Fileinfo"; static final String XFERIDENTIFIER_ELEMENT = "JxtaXfer:Identifier"; static final int XFERFILEINFO_VERSION = 2; static final String XFERSEQUENCE_ELEMENT = "JxtaXfer:Sequence"; static final String XFERDATA_ELEMENT = "JxtaXfer:Data"; private ShellEnv env = null; private static final long MAX_SEARCH_TIME = 60 * 1000; // 1 Minutes private static final int WAITINGTIME = 2 * 1000; // 2 seconds private static final int MAXRETRIES = 5; public static final String XFERUSERNAME = "JxtaXferUserName"; public static final String SftpIDTag = "JxtaSftpPipeID"; private static final String ENVNAME = "jxtaxfer"; private static final String XFERSERVICENAME = "jxtaxfer"; PeerGroup group; DiscoveryService discovery; private int syntaxError() { consoleMessage(getCmdShortName() + "\n\t( (\"register\" [-s|-p] userName) |" + "\n\t( (\"login\" [-e | -s] userName) |" + "\n\t( (\"logout\" userName) |" + "\n\t( (\"send\" [-b blockSize] [-d destAddr] [[-p destPID]] [-u userName] [-a] [-s] destUserName filename) |" + "\n\t( (\"search\") )"); return ShellApp.appParamError; } @Override public String getDescription() { return "Send a file to another peer"; } @Override public void help() { println("NAME"); println(" xfer - send a file to another peer "); println(" "); println("SYNOPSIS"); println(" "); println("xfer ( (\"register\" [-s|-p] userName) |"); println(" ( (\"login\" [-e | -s] userName) |"); println(" ( (\"logout\" userName) |"); println(" ( (\"send\" [-b blockSize] [-d destAddr] [[-p destPID]] [-u userName] [-a] [-s] destUserName filename) |"); println(" ( (\"search\") )"); println(" "); println("DESCRIPTION"); println(" "); println(" The 'xfer' command implements a file transfer between peers. The file transfer "); println(" can be completed using either pipes or via direct endpoint communication. "); println(" "); println("OPTIONS"); println(" "); println(" register Register a new user name "); println(" [-s] Use a secure pipe."); println(" [-p] Use a propagate pipe."); println(" username The name of the new user."); println(" "); println(" login Login user"); println(" [-e] Create an Endpoint Listener rather than an Input Pipe Listener."); println(" [-s] Create a Server Socket rather than an Input Pipe Listener."); println(" username The name of the user."); println(" "); println(" logout Logout user"); println(" username The name of the user."); println(" "); println(" send Send a file "); println(" [-b blockSize] Chunk size in bytes to break file into."); println(" [-d destAddr] Destination endpoint."); println(" [[-p peerID]] Destination peer id for pipe. Can be repeated."); println(" [-u userName] Source user name. Receiver will see this user as the sender."); println(" [-a] Asynchronously send file. Used with Endpoint Listener mode."); println(" [-s] Send file via socket."); println(" destUserName The name of the intended recipient."); println(" filename The path of a local file to be sent."); println(" "); println(" search Search for users."); println(" "); println("EXAMPLE"); println(" "); println(" JXTA>xfer register me"); println(" JXTA>xfer login me"); println(" JXTA>xfer send -u me you /tmp/nihow.jpg"); println(" "); println("This example shows how a new user 'me' can register and log into xfer,"); println("and send a file to the user 'you'. User 'you' needs to be similarly"); println("registered and logged on. The above file is written as /xfer/nihow.jpg"); println(" "); println("SEE ALSO"); println(" "); println(" talk, sftp"); println(" "); } public int startApp(String[] argv) { env = getEnv(); ShellObject obj = env.get("stdgroup"); group = (PeerGroup) obj.getObject(); discovery = group.getDiscoveryService(); // start processing args if (argv.length < 1) { return syntaxError(); } String command = argv[0].toLowerCase(); if ("register".equals(command)) { boolean secure = false; boolean propagate = false; String userName; if (argv.length < 2) { consoleMessage("Missing <userName>"); return syntaxError(); } GetOpt options = new GetOpt(argv, 1, "ps"); while (true) { int option = options.getNextOption(); if (-1 == option) { break; } switch (option) { case'p': propagate = true; break; case's': secure = true; break; default: consoleMessage("Unrecognized option"); return syntaxError(); } if (propagate && secure) { consoleMessage("Secure or propagate but not both allowed"); return syntaxError(); } } userName = options.getNextParameter(); if (null == userName) { consoleMessage("Missing <userName>"); return syntaxError(); } String pipeType = PipeService.UnicastType; if (secure) { pipeType = PipeService.UnicastSecureType; } if (propagate) { pipeType = PipeService.PropagateType; } return registerNewUser(userName, pipeType); } else if ("login".equals(command)) { boolean endpoint = false; boolean socket = false; String userName; if (argv.length < 2) { consoleMessage("Missing userName"); return syntaxError(); } GetOpt options = new GetOpt(argv, 1, "es"); while (true) { int option = options.getNextOption(); if (-1 == option) { break; } switch (option) { case'e': endpoint = true; break; case's': socket = true; break; default: consoleMessage("Unrecognized option"); return syntaxError(); } } userName = options.getNextParameter(); if (null == userName) { consoleMessage("Missing userName"); return syntaxError(); } if ( socket && endpoint ) { consoleMessage("Only one of 'socket' or 'endpoint' may be enabled."); return syntaxError(); } return login(userName, endpoint, socket ); } else if ("logout".equals(command)) { String userName; if (argv.length < 2) { consoleMessage("Missing userName"); return syntaxError(); } GetOpt options = new GetOpt(argv, 1, ""); while (true) { //FIXME this does not loop int option = options.getNextOption(); if (-1 == option) { break; } switch (option) { default: consoleMessage("Unrecognized option"); return syntaxError(); } } userName = options.getNextParameter(); if (null == userName) { consoleMessage("Missing userName"); return syntaxError(); } return logout(userName); } else if ("send".equals(command)) { int blockSize = 15360; EndpointAddress destAddr = null; Set<PeerID> destPIDs = new HashSet<PeerID>(); String srcUserName = null; String destUserName; String filename; boolean async = false; boolean socket = false; if (argv.length < 3) { consoleMessage("Missing destination or filename"); return syntaxError(); } GetOpt options = new GetOpt(argv, 1, "ab:d:p:su:"); while (true) { int option = options.getNextOption(); if (-1 == option) { break; } switch (option) { case'a': async = true; break; case'b': try { blockSize = Integer.parseInt(options.getOptionArg()); } catch (NumberFormatException badnum) { consoleMessage("bad value for block size"); return syntaxError(); } if (blockSize < 1) { consoleMessage("bad value for block size"); return syntaxError(); } break; case'd': try { destAddr = new EndpointAddress(options.getOptionArg()); } catch (Throwable bad) { consoleMessage("bad endpoint address"); return syntaxError(); } break; case'p': try { URI peerID = new URI(options.getOptionArg()); destPIDs.add((PeerID) IDFactory.fromURI(peerID)); } catch (ClassCastException badID) { consoleMessage("ID is not a peer ID"); return syntaxError(); } catch (URISyntaxException badID) { consoleMessage("bad peer ID"); return syntaxError(); } break; case's': socket = true; break; case'u': srcUserName = options.getOptionArg(); break; default: consoleMessage("Unrecognized option"); return syntaxError(); } } destUserName = options.getNextParameter(); if (null == destUserName) { consoleMessage("Missing userName"); return syntaxError(); } filename = options.getNextParameter(); if (null == filename) { consoleMessage("Missing filename"); return syntaxError(); } if (null != destAddr) { destAddr = new EndpointAddress(destAddr, XFERSERVICENAME, destUserName); } if ((null != destAddr) && !destPIDs.isEmpty()) { consoleMessage("Cannot specify both a destination endpoint address and destination peers"); return syntaxError(); } return sendFile(filename, srcUserName, destUserName, destAddr, blockSize, destPIDs, async, socket); } else if ("search".equals(command)) { return findUserAdvs(); } consoleMessage("Unrecognized Command"); return syntaxError(); } /** * Register */ private int registerNewUser(String userName, String type) { // Check if there is already a registered user of the sme name. print("# " + getCmdShortName() + " - Searching for existing advertisement for user '" + userName + "'"); PipeAdvertisement adv = findUserAdv(userName); if (adv != null) { consoleMessage("Sorry, user '" + userName + "' is already registered"); return ShellApp.appMiscError; } try { // Create a pipe advertisement for this pipe. adv = (PipeAdvertisement) AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType()); } catch (Exception all) { printStackTrace("Advertisement document could not be created", all); return ShellApp.appMiscError; } adv.setPipeID(IDFactory.newPipeID(group.getPeerGroupID())); adv.setName(XFERUSERNAME + "." + userName); adv.setType(type); try { // Save the document into the public folder discovery.publish(adv, DiscoveryService.DEFAULT_LIFETIME, DiscoveryService.DEFAULT_EXPIRATION); discovery.remotePublish(adv, DiscoveryService.DEFAULT_EXPIRATION); } catch (Exception e2) { printStackTrace("Advertisement could not be published.", e2); return ShellApp.appMiscError; } consoleMessage("Created advertisement for user '" + userName + "'."); return ShellApp.appNoError; } /** * Stop the receiver daemon for the specified user name. * * @param userName user to log in. * @param endpoint if false, login as an input pipe listener or if true, login as an endpoint listener * @return result code */ private int login(String userName, boolean endpoint, boolean socket ) { PipeAdvertisement adv = null; if (isDaemonRunning(userName)) { consoleMessage("User '" + userName + "' is already listening"); return ShellApp.appMiscError; } if (!endpoint) { print("# " + getCmdShortName() + " - Searching for Advertisement for user '" + userName + "'"); adv = findUserAdv(userName); if (adv == null) { consoleMessage("User '" + userName + "' is not a registered user"); return ShellApp.appMiscError; } } return runDaemon(userName, adv, socket) ? ShellApp.appNoError : ShellApp.appMiscError; } /** * Stop the receiver daemon for the specified user name. * * @param userName user to log out. * @return result code */ private int logout(String userName) { if (!isDaemonRunning(userName)) { consoleMessage("User '" + userName + "' is not listening"); return ShellApp.appMiscError; } consoleMessage("Stoping listener for '" + userName + "'"); return stopDaemon(userName) ? ShellApp.appNoError : ShellApp.appMiscError; } /** * Send a file to a remote peer. * * @param filename the full local path of the file to send. * @param srcUserName optionally our user name otherwise null. * @param destUserName the name of the destination user. * @param destAddr destination peer for direct endpoint transfers. * @param blockSize block size in bytes to segment file into. * @param async use async messengers to send file segments. * @return result code */ private int sendFile(String filename, String srcUserName, String destUserName, EndpointAddress destAddr, int blockSize, Set<? extends ID> destPIDs, boolean async, boolean socket ) { XferDaemon srcDaemon = null; // get the src daemon if any if (null != srcUserName) { srcDaemon = getDaemon(srcUserName); if (null == srcDaemon) { consoleMessage("User '" + srcUserName + "' is not logged in."); return ShellApp.appMiscError; } } File f = new File(filename); if (!f.exists()) { consoleMessage("file '" + filename + "' not found."); return ShellApp.appMiscError; } FileSender sender = new FileSender(group, getOutputConsPipe(), srcDaemon, blockSize, async, f); PipeAdvertisement adv = null; if (null == destAddr) { // Locate target name print("# " + getCmdShortName() + " - Searching for advertisement for user '" + destUserName + "'."); adv = findUserAdv(destUserName); if (adv == null) { consoleMessage("Advertisement for user '" + destUserName + "' not found."); return ShellApp.appMiscError; } consoleMessage("Found advertisement for user '" + destUserName + "' attempting to connect to " + adv.getType()); // Try and connect to target if( !socket ) { try { getGroup().getPipeService().createOutputPipe(adv, destPIDs, sender); } catch (Throwable all) { printStackTrace("failure reaching user '" + destUserName + "'.", all); return ShellApp.appMiscError; } } } else { // asynchronously get a messenger to the destination address. consoleMessage("Getting messenger for '" + destAddr + "'"); boolean working = group.getEndpointService().getMessenger(sender, destAddr, null); if (!working) { consoleMessage("Could not get messenger for '" + destAddr + "'."); return ShellApp.appMiscError; } } Socket connectSocket = null; synchronized (sender) { long start = System.currentTimeMillis(); long finish = start + MAX_SEARCH_TIME; while ((!sender.resolved) && (System.currentTimeMillis() < finish)) { try { long waitFor = Math.max(1, finish - System.currentTimeMillis()); waitFor = Math.min(waitFor, 30 * 1000); consoleMessage("Please be patient ...(" + ((finish - System.currentTimeMillis()) / 1000) + " secs)"); if( !socket ) { sender.wait(waitFor); } else { try { connectSocket = new JxtaSocket(group, adv, (int) MAX_SEARCH_TIME); sender.resolved = true; } catch (Throwable all) { printStackTrace("failure reaching user '" + destUserName + "'.", all); return ShellApp.appMiscError; } } } catch (InterruptedException woken) { Thread.interrupted(); } } if (!sender.resolved) { if (null != adv) { getGroup().getPipeService().removeOutputPipeListener(adv.getPipeID(), sender); } consoleMessage("User '" + destUserName + "' is not listening. Try again later."); return ShellApp.appMiscError; } else { consoleMessage("Located user '" + destUserName + "' after " + (System.currentTimeMillis() - start) + " msec."); if( socket ) { sender.socketConnect( connectSocket ); } } } return ShellApp.appNoError; } /** * Search for a specific user by name. */ private PipeAdvertisement findUserAdv(String name) { String attribute = XFERUSERNAME + "." + name; Enumeration each; int currentRetry = 0; while (currentRetry <= MAXRETRIES) { try { print("."); // Look in the local storage each = discovery.getLocalAdvertisements(DiscoveryService.ADV, PipeAdvertisement.NameTag, attribute); while (each.hasMoreElements()) { PipeAdvertisement adv; try { adv = (PipeAdvertisement) each.nextElement(); } catch (ClassCastException skip) { continue; } println(" "); return adv; } // Now, search remote discovery.getRemoteAdvertisements(null, DiscoveryService.ADV, PipeAdvertisement.NameTag, attribute, 2, null); // nap for a while to let responses come in. try { Thread.sleep(WAITINGTIME); } catch (InterruptedException woken) { Thread.interrupted(); } currentRetry++; } catch (Exception ex) { println(" "); printStackTrace("failure locating advertisement", ex); } } println(" "); // adv not found return null; } /** * Search for any users. */ private int findUserAdvs() { String attribute = XFERUSERNAME + "." + "*"; int currentRetry = 0; while (currentRetry <= MAXRETRIES) { try { print("."); // Now, search remote discovery.getRemoteAdvertisements(null, DiscoveryService.ADV, PipeAdvertisement.NameTag, attribute, 20, null); // nap for a while to let responses come in. try { Thread.sleep(WAITINGTIME); } catch (InterruptedException woken) { Thread.interrupted(); } currentRetry++; } catch (Throwable ex) { println(" "); printStackTrace("Failure locating advertisement", ex); } } println(" "); // Look in the local storage Enumeration each = null; try { each = discovery.getLocalAdvertisements(DiscoveryService.ADV, PipeAdvertisement.NameTag, attribute); } catch (IOException failed) { printStackTrace("Failure locating advertisement", failed); } while (each.hasMoreElements()) { PipeAdvertisement adv; try { adv = (PipeAdvertisement) each.nextElement(); } catch (ClassCastException skip) { continue; } println(adv.getName().substring(XFERUSERNAME.length() + 1)); } return ShellApp.appNoError; } /** * Create a new daemon for the specified user name. * * @param userName user to log in. * @param adv optional pipe advertisement. If null then endpoint listener will be created. * @return if true then daemon was registered successfully otherwise false. */ private boolean runDaemon(String userName, PipeAdvertisement adv, boolean socket) { try { XferDaemon daemon = new XferDaemon(getOutputConsPipe(), group, userName, adv, socket); daemon.setDaemon(true); daemon.start(); // Store this object String dname = ENVNAME + "." + userName + "@" + (group.getPeerGroupAdvertisement()).getName(); ShellObject<?> obj = new ShellObject<XferDaemon>("Xfer Deamon for " + userName + "@" + (group.getPeerGroupAdvertisement()).getName(), daemon); env.add(dname, obj); consoleMessage("Login of user '" + userName + "' successful."); return true; } catch (Throwable ex) { printStackTrace("Failed to create daemon for user '" + userName + "'.", ex); } return false; } /** * Check if a daemon is running for the specified user name. * * @param userName user to check. * @return if true then daemon was found otherwise false. */ private boolean isDaemonRunning(String userName) { return (null != getDaemon(userName)); } /** * Gets the daemon for the specified user name * * @param userName The user name to get the XferDaemon object for * @return the XferDaemon for the specified user name if available, otherwise null. */ private XferDaemon getDaemon(String userName) { String dname = ENVNAME + "." + userName + "@" + (group.getPeerGroupAdvertisement()).getName(); ShellObject obj = env.get(dname); if (null == obj) { return null; } if (!XferDaemon.class.isInstance(obj.getObject())) { throw new RuntimeException(dname + " is not an XferDaemon object"); } return (XferDaemon) obj.getObject(); } /** * Stops the daemon for the specified user name * * @param userName The user name to close the XferDaemon */ private boolean stopDaemon(String userName) { String dname = ENVNAME + "." + userName + "@" + (group.getPeerGroupAdvertisement()).getName(); XferDaemon d = getDaemon(userName); if (null == d) { return false; } try { d.close(); env.remove(dname); return true; } catch (Throwable ex) { printStackTrace("Cannot stop daemon for '" + userName + "'.", ex); return false; } } }