Package com.xebialabs.overthere.ssh

Source Code of com.xebialabs.overthere.ssh.SshScpFile$LsResults

/*
* Copyright (c) 2008-2014, XebiaLabs B.V., All rights reserved.
*
*
* Overthere is licensed under the terms of the GPLv2
* <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most XebiaLabs Libraries.
* There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
* this software, see the FLOSS License Exception
* <http://github.com/xebialabs/overthere/blob/master/LICENSE>.
*
* 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; version 2
* of the License.
*
* 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., 51 Franklin St, Fifth
* Floor, Boston, MA 02110-1301  USA
*/
package com.xebialabs.overthere.ssh;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xebialabs.overthere.CmdLine;
import com.xebialabs.overthere.OverthereFile;
import com.xebialabs.overthere.RuntimeIOException;
import com.xebialabs.overthere.util.CapturingOverthereExecutionOutputHandler;

import net.schmizz.sshj.xfer.LocalFileFilter;
import net.schmizz.sshj.xfer.LocalSourceFile;
import net.schmizz.sshj.xfer.scp.SCPUploadClient;

import static com.xebialabs.overthere.CmdLine.build;
import static com.xebialabs.overthere.ssh.SshConnection.NOCD_PSEUDO_COMMAND;
import static com.xebialabs.overthere.util.CapturingOverthereExecutionOutputHandler.capturingHandler;
import static com.xebialabs.overthere.util.LoggingOverthereExecutionOutputHandler.loggingErrorHandler;
import static com.xebialabs.overthere.util.LoggingOverthereExecutionOutputHandler.loggingOutputHandler;
import static com.xebialabs.overthere.util.MultipleOverthereExecutionOutputHandler.multiHandler;
import static com.xebialabs.overthere.util.NullOverthereExecutionOutputHandler.swallow;
import static java.lang.String.format;

/**
* A file on a host connected through SSH w/ SCP.
*/
class SshScpFile extends SshFile<SshScpConnection> {

    private static final String PERMISSIONS_TOKEN_PATTERN = "[dl\\-]([r\\-][w\\-][xsStT\\-]){3}[@\\.\\+]*";

    private static Pattern permissionsTokenPattern = Pattern.compile(PERMISSIONS_TOKEN_PATTERN);

    /**
     * Constructs an SshScpOverthereFile
     *
     * @param connection the connection to the host
     * @param remotePath the path of the file on the host
     */
    public SshScpFile(SshScpConnection connection, String remotePath) {
        super(connection, remotePath);
    }

    @Override
    public boolean exists() {
        return getFileInfo().exists;
    }

    @Override
    public boolean canRead() {
        return getFileInfo().canRead;
    }

    @Override
    public boolean canWrite() {
        return getFileInfo().canWrite;
    }

    @Override
    public boolean canExecute() {
        return getFileInfo().canExecute;
    }

    @Override
    public boolean isFile() {
        return getFileInfo().isFile;
    }

    @Override
    public boolean isDirectory() {
        return getFileInfo().isDirectory;
    }

    @Override
    public long lastModified() {
        // FIXME: Implement by parsing the date output of `ls -l`
        throw new UnsupportedOperationException();
    }

    @Override
    public long length() {
        return getFileInfo().length;
    }

    /**
     * Gets information about the file by executing "ls -ld" on it.
     *
     * @return the information about the file, never <code>null</code>.
     * @throws RuntimeIOException if an I/O exception occurs
     */
    public LsResults getFileInfo() throws RuntimeIOException {
        logger.debug("Retrieving file info of {}", this);

        CmdLine lsCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.getFileInfoCommand, getPath());
        LsResults results = new LsResults();
        CapturingOverthereExecutionOutputHandler capturedOutput = capturingHandler();
        int errno = executeCommand(capturedOutput, swallow(), lsCmdLine);
        if (errno == 0) {
            for (int i = capturedOutput.getOutputLines().size() - 1; i >= 0; i--) {
                if (parseLsOutputLine(results, capturedOutput.getOutputLines().get(i))) {
                    results.exists = true;
                    break;
                }
            }

            if (!results.exists) {
                throw new RuntimeIOException("ls -ld " + getPath() + " returned " + errno + " but its output is unparseable: " + capturedOutput.getOutput());
            }
        } else {
            results.exists = false;
        }

        logger.debug("Listed file {}: exists={}, isDirectory={}, length={}, canRead={}, canWrite={}, canExecute={}", new Object[]{this, results.exists,
                results.isDirectory, results.length
                , results.canRead, results.canWrite, results.canExecute});

        return results;
    }

    protected boolean parseLsOutputLine(LsResults results, String outputLine) {
        StringTokenizer outputTokens = new StringTokenizer(outputLine);
        if (outputTokens.countTokens() < 5) {
            logger.debug("Not parsing ls output line [{}] because it has less than 5 tokens", outputLine);
            return false;
        }

        String permissions = outputTokens.nextToken();
        if (!permissionsTokenPattern.matcher(permissions).matches()) {
            logger.debug("Not parsing ls output line [{}] because it the first token does not match the pattern for permissions [" + PERMISSIONS_TOKEN_PATTERN
                    + "]", outputLine);
            return false;
        }

        logger.debug("Parsing ls output line [{}]", outputLine);
        outputTokens.nextToken(); // inodelinks
        outputTokens.nextToken(); // owner
        outputTokens.nextToken(); // group
        String size = outputTokens.nextToken();

        results.isFile = permissions.length() >= 1 && permissions.charAt(0) == '-';
        results.isDirectory = permissions.length() >= 1 && permissions.charAt(0) == 'd';
        results.canRead = permissions.length() >= 2 && permissions.charAt(1) == 'r';
        results.canWrite = permissions.length() >= 3 && permissions.charAt(2) == 'w';
        results.canExecute = permissions.length() >= 4 && (permissions.charAt(3) == 'x' || permissions.charAt(3) == 's' || permissions.charAt(3) == 't');
        try {
            results.length = Integer.parseInt(size);
        } catch (NumberFormatException exc) {
            logger.warn("Cannot parse length of " + this.getPath() + " from ls output: " + outputLine + ". Length will be reported as -1.", exc);
        }
        return true;
    }

    /**
     * Holds results of an ls call
     */
    public static class LsResults {
        public boolean exists;
        public boolean isFile;
        public boolean isDirectory;
        public long length = -1;

        public boolean canRead;
        public boolean canWrite;
        public boolean canExecute;
    }

    @Override
    public InputStream getInputStream() throws RuntimeIOException {
        try {
            final File tempFile = File.createTempFile("scp_download", ".tmp");
            tempFile.deleteOnExit();

            logger.debug("Downloading contents of {} to temporary file {}", this, tempFile);
            connection.getSshClient().newSCPFileTransfer().download(getPath(), tempFile.getPath());

            logger.debug("Opening input stream to temporary file {} to retrieve contents downloaded from {}. Temporary file will be deleted when the stream is closed", tempFile, this);
            return asBuffered(new FileInputStream(tempFile) {
                @Override
                public void close() throws IOException {
                    logger.debug("Closing input stream to temporary file {}", tempFile);
                    try {
                        super.close();
                    } finally {
                        logger.debug("Deleting temporary file {}", tempFile);
                        tempFile.delete();
                    }

                }
            });
        } catch (IOException exc) {
            throw new RuntimeIOException(format("Cannot open %s for reading: %s", this, exc.toString()), exc);
        }
    }

    @Override
    public OutputStream getOutputStream() throws RuntimeIOException {
        try {
            final File tempFile = File.createTempFile("scp_upload", ".tmp");
            tempFile.deleteOnExit();

            logger.debug("Opening output stream to temporary file {} to store contents to be uploaded to {} when the stream is closed", tempFile, SshScpFile.this);
            return asBuffered(new FileOutputStream(tempFile) {
                @Override
                public void close() throws IOException {
                    logger.debug("Closing output stream to temporary file {}", tempFile);
                    try {
                        super.close();
                    } finally {
                        uploadAndDelete(tempFile);
                    }
                }

                private void uploadAndDelete(File tempFile) throws IOException {
                    logger.debug("Uploading contents of temporary file {} to to {}", tempFile, SshScpFile.this);
                    try {
                        connection.getSshClient().newSCPFileTransfer().upload(tempFile.getPath(), getPath());
                    } finally {
                        logger.debug("Deleting temporary file {}", tempFile);
                        tempFile.delete();
                    }
                }
            });
        } catch (IOException exc) {
            throw new RuntimeIOException(format("Cannot open %s for writing: %s", this, exc.toString()), exc);
        }
    }

    @Override
    public List<OverthereFile> listFiles() {
        logger.debug("Listing directory {}", this);

        CmdLine lsCmdLine = build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.listFilesCommand, getPath());

        CapturingOverthereExecutionOutputHandler capturedStdout = capturingHandler();
        CapturingOverthereExecutionOutputHandler capturedStderr = capturingHandler();
        int errno = executeCommand(multiHandler(loggingOutputHandler(logger), capturedStdout), multiHandler(loggingErrorHandler(logger), capturedStderr), lsCmdLine);
        if (errno != 0) {
            throw new RuntimeIOException("Cannot list directory " + this + ": " + capturedStderr.getOutput() + " (errno=" + errno + ")");
        }

        List<OverthereFile> files = new ArrayList<OverthereFile>();
        for (String lsLine : capturedStdout.getOutputLines()) {
            // Filter out the '.' and '..'
            if (!(".".equals(lsLine) || "..".equals(lsLine))) {
                files.add(connection.getFile(this, lsLine));
            }
        }

        return files;
    }

    @Override
    public void mkdir() {
        logger.debug("Creating directory {}", this);

        mkdir(connection.mkdirCommand);
    }

    @Override
    public void mkdirs() {
        logger.debug("Creating directories {}", this);

        mkdir(connection.mkdirsCommand);
    }

    protected void mkdir(String command) throws RuntimeIOException {
        CmdLine mkdirCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(command, getPath());
        executeAndThrowOnErrorCode(mkdirCmdLine, "Cannot create directory or -ies " + this);

        if (logger.isDebugEnabled()) {
            logger.debug("Created directory " + this + " (using command: " + command + ")");
        }
    }

    @Override
    public void renameTo(OverthereFile dest) {
        logger.debug("Renaming {} to {}", this, dest);

        if (dest instanceof SshScpFile) {
            SshScpFile sshScpDestFile = (SshScpFile) dest;
            if (sshScpDestFile.getConnection() == getConnection()) {
                CmdLine mvCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.renameToCommand, getPath(), sshScpDestFile.getPath());
                executeAndThrowOnErrorCode(mvCmdLine, "Cannot rename file/directory " + this);
            } else {
                throw new RuntimeIOException("Cannot rename :ssh:" + connection.sshConnectionType.toString().toLowerCase() + ": file/directory " + this
                        + " to file/directory "
                        + dest + " because it is in a different connection");
            }
        } else {
            throw new RuntimeIOException("Cannot rename :ssh:" + connection.sshConnectionType.toString().toLowerCase() + ": file/directory " + this
                    + " to non-:ssh:"
                    + connection.sshConnectionType.toString().toLowerCase() + ": file/directory " + dest);
        }
    }

    @Override
    public void setExecutable(boolean executable) {
        logger.debug("Setting execute permission on {} to {}", this, executable);

        CmdLine chmodCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(executable ? connection.setExecutableCommand : connection.setNotExecutableCommand, getPath());
        executeAndThrowOnErrorCode(chmodCmdLine, "Cannot set execute permission on file " + this + " to " + executable);
    }

    @Override
    protected void deleteDirectory() {
        logger.debug("Deleting directory {}", this);

        CmdLine rmdirCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.deleteDirectoryCommand, getPath());
        executeAndThrowOnErrorCode(rmdirCmdLine, "Cannot delete directory " + this);
    }

    @Override
    protected void deleteFile() {
        logger.debug("Deleting file {}", this);

        CmdLine rmCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.deleteFileCommand, getPath());
        executeAndThrowOnErrorCode(rmCmdLine, "Cannot delete file " + this);
    }

    @Override
    public void deleteRecursively() throws RuntimeIOException {
        logger.debug("Recursively deleting file or directory {}", this);

        CmdLine rmCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.deleteRecursivelyCommand, getPath());
        executeAndThrowOnErrorCode(rmCmdLine, "Cannot recursively delete file or directory " + this);
    }

    @Override
    protected void copyFrom(OverthereFile source) {
        logger.debug("Copying file or directory {} to {}", source, this);

        SCPUploadClient uploadClient = connection.getSshClient().newSCPFileTransfer().newSCPUploadClient();

        try {
            if (source.isDirectory() && this.exists()) {
                for (OverthereFile sourceFile : source.listFiles()) {
                    uploadClient.copy(new OverthereFileLocalSourceFile(sourceFile), getPath());
                }
            } else {
                uploadClient.copy(new OverthereFileLocalSourceFile(source), getPath());
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Cannot copy " + source + " to " + this + ": " + e.toString(), e);
        }
    }

    private void executeAndThrowOnErrorCode(CmdLine mkdirCmdLine, String message) {
        CapturingOverthereExecutionOutputHandler capturedStderr = capturingHandler();
        int errno = executeCommand(loggingOutputHandler(logger), multiHandler(loggingErrorHandler(logger), capturedStderr), mkdirCmdLine);
        if (errno != 0) {
            throw new RuntimeIOException(format("%s: %s (errno=%d)", message, capturedStderr.getOutput(), errno));
        }
    }

    protected static class OverthereFileLocalSourceFile implements LocalSourceFile {

        private OverthereFile f;

        public OverthereFileLocalSourceFile(OverthereFile f) {
            this.f = f;
        }

        @Override
        public String getName() {
            return f.getName();
        }

        @Override
        public long getLength() {
            return f.length();
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return f.getInputStream();
        }

        @Override
        public int getPermissions() throws IOException {
            return f.isDirectory() ? 0755 : 0644;
        }

        @Override
        public boolean isFile() {
            return f.isFile();
        }

        @Override
        public boolean isDirectory() {
            return f.isDirectory();
        }

        @Override
        public Iterable<? extends LocalSourceFile> getChildren(LocalFileFilter filter) throws IOException {
            List<LocalSourceFile> files = new ArrayList<LocalSourceFile>();
            for (OverthereFile each : f.listFiles()) {
                files.add(new OverthereFileLocalSourceFile(each));
            }
            return files;
        }

        @Override
        public boolean providesAtimeMtime() {
            return false;
        }

        @Override
        public long getLastAccessTime() throws IOException {
            return 0;
        }

        @Override
        public long getLastModifiedTime() throws IOException {
            return 0;
        }

    }

    private static Logger logger = LoggerFactory.getLogger(SshScpFile.class);

}
TOP

Related Classes of com.xebialabs.overthere.ssh.SshScpFile$LsResults

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.