ch.cyberduck.core.Local.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.Local.java

Source

package ch.cyberduck.core;

/*
 * Copyright (c) 2012 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.exception.AccessDeniedException;
import ch.cyberduck.core.exception.LocalAccessDeniedException;
import ch.cyberduck.core.exception.LocalNotfoundException;
import ch.cyberduck.core.exception.NotfoundException;
import ch.cyberduck.core.io.LocalRepeatableFileInputStream;
import ch.cyberduck.core.local.TildeExpander;
import ch.cyberduck.core.local.WorkdirPrefixer;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.serializer.Serializer;
import ch.cyberduck.core.unicode.NFCNormalizer;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.EnumSet;
import java.util.Objects;

public class Local extends AbstractPath implements Referenceable, Serializable {
    private static final Logger log = Logger.getLogger(Local.class);

    /**
     * Absolute path in local file system
     */
    private String path;

    private final LocalAttributes attributes;

    public Local(final String parent, final String name) throws LocalAccessDeniedException {
        this(parent, name, PreferencesFactory.get().getProperty("local.delimiter"));
    }

    public Local(final String parent, final String name, final String delimiter) throws LocalAccessDeniedException {
        this(parent.endsWith(delimiter) ? String.format("%s%s", parent, name)
                : String.format("%s%c%s", parent, CharUtils.toChar(delimiter), name));
    }

    public Local(final Local parent, final String name) throws LocalAccessDeniedException {
        this(parent, name, PreferencesFactory.get().getProperty("local.delimiter"));
    }

    public Local(final Local parent, final String name, final String delimiter) throws LocalAccessDeniedException {
        this(parent.isRoot() ? String.format("%s%s", parent.getAbsolute(), name)
                : String.format("%s%c%s", parent.getAbsolute(), CharUtils.toChar(delimiter), name));
    }

    /**
     * @param name Absolute path
     */
    public Local(final String name) throws LocalAccessDeniedException {
        String path = name;
        if (PreferencesFactory.get().getBoolean("local.normalize.unicode")) {
            path = new NFCNormalizer().normalize(path).toString();
        }
        if (PreferencesFactory.get().getBoolean("local.normalize.tilde")) {
            path = new TildeExpander().expand(path);
        }
        if (PreferencesFactory.get().getBoolean("local.normalize.prefix")) {
            path = new WorkdirPrefixer().normalize(path);
        }
        try {
            this.path = Paths.get(path).toString();
        } catch (InvalidPathException e) {
            throw new LocalAccessDeniedException(
                    String.format("The name %s is not a valid path for the filesystem", path), e);
        }
        this.attributes = new LocalAttributes(path);
    }

    @Override
    public <T> T serialize(final Serializer dict) {
        dict.setStringForKey(path, "Path");
        return dict.getSerialized();
    }

    @Override
    public EnumSet<Type> getType() {
        final EnumSet<Type> set = EnumSet.noneOf(Type.class);
        if (this.isFile()) {
            set.add(Type.file);
        }
        if (this.isDirectory()) {
            set.add(Type.directory);
        }
        if (this.isVolume()) {
            set.add(Type.volume);
        }
        if (this.isSymbolicLink()) {
            set.add(Type.symboliclink);
        }
        return set;
    }

    public boolean isVolume() {
        return null == Paths.get(path).getParent();
    }

    /**
     * This is only returning the correct result if the file already exists.
     *
     * @see Local#exists()
     */
    public boolean isDirectory() {
        if (this.exists()) {
            return Files.isDirectory(Paths.get(path));
        }
        return false;
    }

    /**
     * This is only returning the correct result if the file already exists.
     *
     * @see Local#exists()
     */
    public boolean isFile() {
        if (this.exists()) {
            return Files.isRegularFile(Paths.get(path));
        }
        return false;
    }

    /**
     * Checks whether a given file is a symbolic link.
     *
     * @return true if the file is a symbolic link.
     */
    public boolean isSymbolicLink() {
        return Files.isSymbolicLink(Paths.get(path));
    }

    public Local getSymlinkTarget() throws NotfoundException, LocalAccessDeniedException {
        try {
            // For a link that actually points to something (either a file or a directory),
            // the absolute path is the path through the link, whereas the canonical path
            // is the path the link references.
            return LocalFactory.get(Files.readSymbolicLink(Paths.get(path)).toAbsolutePath().toString());
        } catch (InvalidPathException | IOException e) {
            throw new LocalNotfoundException(String.format("Resolving symlink target for %s failed", path), e);
        }
    }

    public LocalAttributes attributes() {
        return attributes;
    }

    @Override
    public char getDelimiter() {
        return CharUtils.toChar(PreferencesFactory.get().getProperty("local.delimiter"));
    }

    public void mkdir() throws AccessDeniedException {
        try {
            Files.createDirectories(Paths.get(path));
        } catch (IOException e) {
            throw new LocalAccessDeniedException(
                    MessageFormat.format(LocaleFactory.localizedString("Cannot create folder {0}", "Error"), path),
                    e);
        }
    }

    /**
     * Delete the file
     */
    public void delete() throws AccessDeniedException {
        try {
            Files.deleteIfExists(Paths.get(path));
        } catch (IOException e) {
            throw new LocalAccessDeniedException(String.format("Delete %s failed", path), e);
        }
    }

    public AttributedList<Local> list(final Filter<String> filter) throws AccessDeniedException {
        final AttributedList<Local> children = new AttributedList<Local>();
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(path),
                new DirectoryStream.Filter<Path>() {
                    @Override
                    public boolean accept(final Path entry) throws IOException {
                        if (null == entry.getFileName()) {
                            return false;
                        }
                        return filter.accept(entry.getFileName().toString());
                    }
                })) {
            for (Path entry : stream) {
                children.add(LocalFactory.get(entry.toString()));
            }
        } catch (IOException e) {
            throw new LocalAccessDeniedException(String.format("Error listing files in directory %s", path), e);
        }
        return children;
    }

    public AttributedList<Local> list() throws AccessDeniedException {
        return this.list(new NullFilter<>());
    }

    @Override
    public String getAbsolute() {
        return path;
    }

    /**
     * @return Security scoped bookmark outside of sandbox to store in preferences
     */
    public String getBookmark() {
        return path;
    }

    public void setBookmark(final String data) {
        //
    }

    public Local withBookmark(final String data) {
        this.setBookmark(data);
        return this;
    }

    /**
     * @return A shortened path representation.
     */
    public String getAbbreviatedPath() {
        return new TildeExpander().abbreviate(path);
    }

    /**
     * Subclasses may override to return a user friendly representation of the name denoting this path.
     *
     * @return Name of the file
     * @see #getName()
     */
    public String getDisplayName() {
        return this.getName();
    }

    /**
     * @return The last path component.
     */
    @Override
    public String getName() {
        return FilenameUtils.getName(path);
    }

    public Local getVolume() {
        return LocalFactory.get(String.valueOf(this.getDelimiter()));
    }

    public Local getParent() {
        if (this.isVolume()) {
            return this;
        }
        return LocalFactory.get(Paths.get(path).getParent().toString());
    }

    /**
     * @return True if the path exists on the file system.
     */
    public boolean exists() {
        return Files.exists(Paths.get(path));
    }

    public void rename(final Local renamed) throws AccessDeniedException {
        try {
            Files.move(Paths.get(path), Paths.get(renamed.getAbsolute()), StandardCopyOption.REPLACE_EXISTING);
            path = renamed.getAbsolute();
        } catch (IOException e) {
            throw new LocalAccessDeniedException(String.format("Rename failed for %s", renamed), e);
        }
    }

    public void copy(final Local copy) throws AccessDeniedException {
        this.copy(copy, new CopyOptions());
    }

    public void copy(final Local copy, final CopyOptions options) throws AccessDeniedException {
        if (copy.equals(this)) {
            log.warn(String.format("%s and %s are identical. Not copied.", this.getName(), copy.getName()));
        } else {
            if (log.isDebugEnabled()) {
                log.debug(String.format("Copy to %s with options %s", copy, options));
            }
            InputStream in = null;
            OutputStream out = null;
            try {
                in = this.getInputStream();
                out = copy.getOutputStream(options.append);
                IOUtils.copy(in, out);
            } catch (IOException e) {
                throw new LocalAccessDeniedException(e.getMessage(), e);
            } finally {
                IOUtils.closeQuietly(in);
                IOUtils.closeQuietly(out);
            }
        }
    }

    public static final class CopyOptions {
        public boolean append;

        public CopyOptions append(final boolean append) {
            this.append = append;
            return this;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("CopyOptions{");
            sb.append("append=").append(append);
            sb.append('}');
            return sb.toString();
        }
    }

    /**
     * Compares the two files using their path with a string comparision ignoring case.
     * Implementations should override this depending on the case sensitivity of the file system.
     */
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Local)) {
            return false;
        }
        final Local local = (Local) o;
        if (path != null ? !path.equalsIgnoreCase(local.path) : local.path != null) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return path != null ? StringUtils.lowerCase(path).hashCode() : 0;
    }

    public String toURL() {
        return String.format("file:%s", path);
    }

    public InputStream getInputStream() throws AccessDeniedException {
        try {
            return new LocalRepeatableFileInputStream(new File(path));
        } catch (FileNotFoundException e) {
            throw new LocalAccessDeniedException(e.getMessage(), e);
        }
    }

    public OutputStream getOutputStream(final boolean append) throws AccessDeniedException {
        try {
            return new FileOutputStream(new File(path), append);
        } catch (FileNotFoundException e) {
            throw new LocalAccessDeniedException(e.getMessage(), e);
        }
    }

    public Object lock(final boolean interactive) throws AccessDeniedException {
        return null;
    }

    public void release(Object lock) {
        //
    }

    /**
     * @param directory Parent directory
     * @return True if this is a child in the path hierarchy of the argument passed
     */
    public boolean isChild(final Local directory) {
        if (this.isRoot()) {
            // Root cannot be a child of any other path
            return false;
        }
        if (Objects.equals(this.parent(this.getAbsolute()), this.parent(directory.getAbsolute()))) {
            // Cannot be a child if the same parent
            return false;
        }
        final String prefix = FilenameUtils.getPrefix(this.getAbsolute());
        String parent = this.getAbsolute();
        while (!parent.equals(prefix)) {
            parent = this.parent(parent);
            if (directory.getAbsolute().equals(parent)) {
                return true;
            }
        }
        return false;
    }

    private String parent(final String absolute) {
        final String prefix = FilenameUtils.getPrefix(absolute);
        if (absolute.equals(prefix)) {
            return null;
        }
        int index = absolute.length() - 1;
        if (absolute.charAt(index) == this.getDelimiter()) {
            if (index > 0) {
                index--;
            }
        }
        final int cut = absolute.lastIndexOf(this.getDelimiter(), index);
        if (cut > FilenameUtils.getPrefixLength(absolute)) {
            return absolute.substring(0, cut);
        }
        return String.valueOf(prefix);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Local{");
        sb.append("path='").append(path).append('\'');
        sb.append('}');
        return sb.toString();
    }
}