ch.cyberduck.core.sftp.SFTPPath.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.sftp.SFTPPath.java

Source

package ch.cyberduck.core.sftp;

/*
 *  Copyright (c) 2007 David Kocher. All rights reserved.
 *  http://cyberduck.ch/
 *
 *  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 of the License, 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.
 *
 *  Bug fixes, suggestions and comments should be sent to:
 *  dkocher@cyberduck.ch
 */

import ch.cyberduck.core.AbstractPath;
import ch.cyberduck.core.AttributedList;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.Permission;
import ch.cyberduck.core.Preferences;
import ch.cyberduck.core.Protocol;
import ch.cyberduck.core.StreamListener;
import ch.cyberduck.core.date.UserDateFormatterFactory;
import ch.cyberduck.core.i18n.Locale;
import ch.cyberduck.core.io.BandwidthThrottle;
import ch.cyberduck.core.io.IOResumeException;
import ch.cyberduck.core.local.Local;
import ch.cyberduck.core.transfer.TransferStatus;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;

import ch.ethz.ssh2.SCPClient;
import ch.ethz.ssh2.SFTPException;
import ch.ethz.ssh2.SFTPInputStream;
import ch.ethz.ssh2.SFTPOutputStream;
import ch.ethz.ssh2.SFTPv3Client;
import ch.ethz.ssh2.SFTPv3DirectoryEntry;
import ch.ethz.ssh2.SFTPv3FileAttributes;
import ch.ethz.ssh2.SFTPv3FileHandle;

/**
 * @version $Id: SFTPPath.java 10837 2013-04-10 20:42:42Z dkocher $
 */
public class SFTPPath extends Path {
    private static final Logger log = Logger.getLogger(SFTPPath.class);

    private final SFTPSession session;

    public SFTPPath(SFTPSession s, String parent, String name, int type) {
        super(parent, name, type);
        this.session = s;
    }

    public SFTPPath(SFTPSession s, String path, int type) {
        super(path, type);
        this.session = s;
    }

    public SFTPPath(SFTPSession s, String parent, Local file) {
        super(parent, file);
        this.session = s;
    }

    public <T> SFTPPath(SFTPSession s, T dict) {
        super(dict);
        this.session = s;
    }

    @Override
    public SFTPSession getSession() {
        return session;
    }

    @Override
    public AttributedList<Path> list(final AttributedList<Path> children) {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat
                    .format(Locale.localizedString("Listing directory {0}", "Status"), this.getName()));

            for (SFTPv3DirectoryEntry f : this.getSession().sftp().ls(this.getAbsolute())) {
                if (f.filename.equals(".") || f.filename.equals("..")) {
                    continue;
                }
                SFTPv3FileAttributes attributes = f.attributes;
                SFTPPath p = new SFTPPath(this.getSession(), this.getAbsolute(), f.filename,
                        attributes.isDirectory() ? DIRECTORY_TYPE : FILE_TYPE);
                p.setParent(this);
                p.readAttributes(attributes);
                children.add(p);
            }
        } catch (IOException e) {
            log.warn("Listing directory failed:" + e.getMessage());
            children.attributes().setReadable(false);
            if (!session.cache().containsKey(this.getReference())) {
                this.error(e.getMessage(), e);
            }
        }
        return children;
    }

    @Override
    public void mkdir() {
        try {
            this.getSession().check();
            this.getSession().message(
                    MessageFormat.format(Locale.localizedString("Making directory {0}", "Status"), this.getName()));

            this.getSession().sftp().mkdir(this.getAbsolute(), Integer.parseInt(
                    new Permission(Preferences.instance().getInteger("queue.upload.permissions.folder.default"))
                            .getOctalString(),
                    8));
        } catch (IOException e) {
            this.error("Cannot create folder {0}", e);
        }
    }

    @Override
    public void rename(AbstractPath renamed) {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat.format(Locale.localizedString("Renaming {0} to {1}", "Status"),
                    this.getName(), renamed));

            if (renamed.exists()) {
                renamed.delete();
            }
            this.getSession().sftp().mv(this.getAbsolute(), renamed.getAbsolute());
        } catch (IOException e) {
            this.error("Cannot rename {0}", e);
        }
    }

    @Override
    public void delete() {
        try {
            this.getSession().check();
            this.getSession().message(
                    MessageFormat.format(Locale.localizedString("Deleting {0}", "Status"), this.getName()));

            if (this.attributes().isFile() || this.attributes().isSymbolicLink()) {
                this.getSession().sftp().rm(this.getAbsolute());
            } else if (this.attributes().isDirectory()) {
                for (AbstractPath child : this.children()) {
                    if (!this.getSession().isConnected()) {
                        break;
                    }
                    child.delete();
                }
                this.getSession().message(
                        MessageFormat.format(Locale.localizedString("Deleting {0}", "Status"), this.getName()));

                this.getSession().sftp().rmdir(this.getAbsolute());
            }
        } catch (IOException e) {
            this.error("Cannot delete {0}", e);
        }
    }

    protected void readAttributes() throws IOException {
        this.readAttributes(this.getSession().sftp().stat(this.getAbsolute()));
    }

    protected void readAttributes(SFTPv3FileAttributes attributes) {
        if (null != attributes.size) {
            if (this.attributes().isFile()) {
                this.attributes().setSize(attributes.size);
            }
        }
        String perm = attributes.getOctalPermissions();
        if (null != perm) {
            try {
                String octal = Integer.toOctalString(attributes.permissions);
                this.attributes()
                        .setPermission(new Permission(Integer.parseInt(octal.substring(octal.length() - 4))));
            } catch (IndexOutOfBoundsException e) {
                log.warn(String.format("Failure parsing mode:%s", e.getMessage()));
            } catch (NumberFormatException e) {
                log.warn(String.format("Failure parsing mode:%s", e.getMessage()));
            }
        }
        if (null != attributes.uid) {
            this.attributes().setOwner(attributes.uid.toString());
        }
        if (null != attributes.gid) {
            this.attributes().setGroup(attributes.gid.toString());
        }
        if (null != attributes.mtime) {
            this.attributes().setModificationDate(Long.parseLong(attributes.mtime.toString()) * 1000L);
        }
        if (null != attributes.atime) {
            this.attributes().setAccessedDate(Long.parseLong(attributes.atime.toString()) * 1000L);
        }
        if (attributes.isSymlink()) {
            try {
                String target = this.getSession().sftp().readLink(this.getAbsolute());
                if (!target.startsWith(String.valueOf(Path.DELIMITER))) {
                    target = Path
                            .normalize(this.getParent().getAbsolute() + String.valueOf(Path.DELIMITER) + target);
                }
                this.setSymlinkTarget(target);
                SFTPv3FileAttributes targetAttributes = this.getSession().sftp().stat(target);
                if (targetAttributes.isDirectory()) {
                    this.attributes().setType(SYMBOLIC_LINK_TYPE | DIRECTORY_TYPE);
                } else if (targetAttributes.isRegularFile()) {
                    this.attributes().setType(SYMBOLIC_LINK_TYPE | FILE_TYPE);
                }
            } catch (IOException e) {
                log.warn(String.format("Cannot read symbolic link target of %s:%s", this.getAbsolute(),
                        e.getMessage()));
                this.attributes().setType(FILE_TYPE);
            }
        }
    }

    protected void writeAttributes(SFTPv3FileAttributes attributes) throws IOException {
        this.getSession().sftp().setstat(this.getAbsolute(), attributes);
    }

    @Override
    public void readSize() {
        if (this.attributes().isFile()) {
            try {
                this.getSession().check();
                this.getSession().message(MessageFormat
                        .format(Locale.localizedString("Getting size of {0}", "Status"), this.getName()));

                this.readAttributes();
            } catch (IOException e) {
                this.error("Cannot read file attributes", e);
            }
        }
    }

    @Override
    public void readTimestamp() {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat
                    .format(Locale.localizedString("Getting timestamp of {0}", "Status"), this.getName()));

            this.readAttributes();
        } catch (IOException e) {
            this.error("Cannot read file attributes", e);
        }
    }

    @Override
    public void readUnixPermission() {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat
                    .format(Locale.localizedString("Getting permission of {0}", "Status"), this.getName()));

            this.readAttributes();
        } catch (IOException e) {
            this.error("Cannot read file attributes", e);
        }
    }

    @Override
    public void writeOwner(String owner) {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat.format(
                    Locale.localizedString("Changing owner of {0} to {1}", "Status"), this.getName(), owner));

            SFTPv3FileAttributes attr = new SFTPv3FileAttributes();
            attr.uid = new Integer(owner);
            this.writeAttributes(attr);
        } catch (IOException e) {
            this.error("Cannot change owner", e);
        }
    }

    @Override
    public void writeGroup(String group) {
        try {
            this.getSession().check();
            this.getSession().message(MessageFormat.format(
                    Locale.localizedString("Changing group of {0} to {1}", "Status"), this.getName(), group));

            SFTPv3FileAttributes attr = new SFTPv3FileAttributes();
            attr.gid = new Integer(group);
            this.writeAttributes(attr);
        } catch (IOException e) {
            this.error("Cannot change group", e);
        }
    }

    @Override
    public void writeUnixPermission(Permission permission) {
        try {
            this.getSession().check();
            this.writeUnixPermissionImpl(permission);
        } catch (IOException e) {
            this.error("Cannot change permissions", e);
        }
    }

    private void writeUnixPermissionImpl(final Permission permission) throws IOException {
        this.getSession()
                .message(MessageFormat.format(Locale.localizedString("Changing permission of {0} to {1}", "Status"),
                        this.getName(), permission.getOctalString()));

        try {
            SFTPv3FileAttributes attr = new SFTPv3FileAttributes();
            attr.permissions = Integer.parseInt(permission.getOctalString(), 8);
            this.writeAttributes(attr);
        } catch (SFTPException ignore) {
            // We might not be able to change the attributes if we are not the owner of the file
            log.warn(ignore.getMessage());
        } finally {
            this.attributes().clear(false, false, true, false);
        }
    }

    @Override
    public void writeTimestamp(long created, long modified, long accessed) {
        try {
            this.writeModificationDateImpl(modified);
        } catch (IOException e) {
            this.error("Cannot change timestamp", e);
        }
    }

    private void writeModificationDateImpl(long modified) throws IOException {
        this.getSession()
                .message(MessageFormat.format(Locale.localizedString("Changing timestamp of {0} to {1}", "Status"),
                        this.getName(), UserDateFormatterFactory.get().getShortFormat(modified)));
        try {
            SFTPv3FileAttributes attrs = new SFTPv3FileAttributes();
            int t = (int) (modified / 1000);
            // We must both set the accessed and modified time. See AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME
            attrs.atime = t;
            attrs.mtime = t;
            this.writeAttributes(attrs);
        } catch (SFTPException ignore) {
            // We might not be able to change the attributes if we are not the owner of the file
            log.warn(ignore.getMessage());
        } finally {
            this.attributes().clear(true, false, false, false);
        }
    }

    @Override
    public InputStream read(final TransferStatus status) throws IOException {
        InputStream in = null;
        if (Preferences.instance().getProperty("ssh.transfer").equals(Protocol.SFTP.getIdentifier())) {
            final SFTPv3FileHandle handle = this.getSession().sftp().openFileRO(this.getAbsolute());
            in = new SFTPInputStream(handle);
            if (status.isResume()) {
                log.info(String.format("Skipping %d bytes", status.getCurrent()));
                final long skipped = in.skip(status.getCurrent());
                if (skipped < status.getCurrent()) {
                    throw new IOResumeException(
                            String.format("Skipped %d bytes instead of %d", skipped, status.getCurrent()));
                }
            }
            // No parallel requests if the file size is smaller than the buffer.
            this.getSession().sftp().setRequestParallelism(
                    (int) (status.getLength() / Preferences.instance().getInteger("connection.chunksize")) + 1);
        } else if (Preferences.instance().getProperty("ssh.transfer").equals(Protocol.SCP.getIdentifier())) {
            SCPClient scp = this.getSession().openScp();
            scp.setCharset(this.getSession().getEncoding());
            in = scp.get(this.getAbsolute());
        }
        return in;
    }

    @Override
    public void download(BandwidthThrottle throttle, StreamListener listener, final TransferStatus status) {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = this.read(status);
            out = this.getLocal().getOutputStream(status.isResume());
            this.download(in, out, throttle, listener, status);
        } catch (IOException e) {
            this.error("Download failed", e);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    @Override
    public void symlink(String target) {
        if (log.isDebugEnabled()) {
            log.debug("symlink:" + target);
        }
        try {
            this.getSession().check();
            this.getSession().message(
                    MessageFormat.format(Locale.localizedString("Uploading {0}", "Status"), this.getName()));

            this.getSession().sftp().createSymlink(this.getAbsolute(), target);
        } catch (IOException e) {
            this.error("Cannot create file {0}", e);
        }
    }

    @Override
    public OutputStream write(final TransferStatus status) throws IOException {
        final String mode = Preferences.instance().getProperty("ssh.transfer");
        if (mode.equals(Protocol.SFTP.getIdentifier())) {
            SFTPv3FileHandle handle;
            if (status.isResume() && this.exists()) {
                handle = this.getSession().sftp().openFile(this.getAbsolute(),
                        SFTPv3Client.SSH_FXF_WRITE | SFTPv3Client.SSH_FXF_APPEND, null);
            } else {
                handle = this.getSession().sftp().openFile(this.getAbsolute(),
                        SFTPv3Client.SSH_FXF_CREAT | SFTPv3Client.SSH_FXF_TRUNC | SFTPv3Client.SSH_FXF_WRITE, null);
            }
            final OutputStream out = new SFTPOutputStream(handle);
            if (status.isResume()) {
                long skipped = ((SFTPOutputStream) out).skip(status.getCurrent());
                log.info(String.format("Skipping %d bytes", skipped));
                if (skipped < status.getCurrent()) {
                    throw new IOResumeException(
                            String.format("Skipped %d bytes instead of %d", skipped, status.getCurrent()));
                }
            }
            // No parallel requests if the file size is smaller than the buffer.
            this.getSession().sftp().setRequestParallelism(
                    (int) (status.getLength() / Preferences.instance().getInteger("connection.chunksize")) + 1);
            return out;
        } else if (mode.equals(Protocol.SCP.getIdentifier())) {
            SCPClient scp = this.getSession().openScp();
            scp.setCharset(this.getSession().getEncoding());
            return scp.put(this.getName(), status.getLength(), this.getParent().getAbsolute(),
                    "0" + this.attributes().getPermission().getOctalString());
        }
        throw new IOException("Unknown transfer mode:" + mode);
    }

    @Override
    public void upload(final BandwidthThrottle throttle, final StreamListener listener,
            final TransferStatus status) {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = this.getLocal().getInputStream();
            out = this.write(status);
            this.upload(out, in, throttle, listener, status);
        } catch (IOException e) {
            this.error("Upload failed", e);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
    }

    @Override
    public boolean touch() {
        try {
            this.getSession().check();
            this.getSession().message(
                    MessageFormat.format(Locale.localizedString("Uploading {0}", "Status"), this.getName()));

            SFTPv3FileAttributes attr = new SFTPv3FileAttributes();
            Permission permission = new Permission(
                    Preferences.instance().getInteger("queue.upload.permissions.file.default"));
            attr.permissions = Integer.parseInt(permission.getOctalString(), 8);
            this.getSession().sftp().createFile(this.getAbsolute(), attr);
            try {
                // Even if specified above when creating the file handle, we still need to update the
                // permissions after the creating the file. SSH_FXP_OPEN does not support setting
                // attributes in version 4 or lower.
                this.writeUnixPermissionImpl(permission);
            } catch (SFTPException ignore) {
                log.warn(ignore.getMessage());
            }
            return true;
        } catch (IOException e) {
            this.error("Cannot create file {0}", e);
            return false;
        }
    }
}