ch.cyberduck.core.ftp.FTPListService.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.ftp.FTPListService.java

Source

package ch.cyberduck.core.ftp;

/*
 * Copyright (c) 2002-2013 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 feedback@cyberduck.ch
 */

import ch.cyberduck.core.AttributedList;
import ch.cyberduck.core.DisabledCancelCallback;
import ch.cyberduck.core.DisabledHostKeyCallback;
import ch.cyberduck.core.DisabledLoginCallback;
import ch.cyberduck.core.DisabledPasswordStore;
import ch.cyberduck.core.HostPasswordStore;
import ch.cyberduck.core.ListProgressListener;
import ch.cyberduck.core.ListService;
import ch.cyberduck.core.LoginCallback;
import ch.cyberduck.core.LoginConnectionService;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.PathCache;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.InteroperabilityException;
import ch.cyberduck.core.ftp.parser.CompositeFileEntryParser;
import ch.cyberduck.core.preferences.PreferencesFactory;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPCmd;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

public class FTPListService implements ListService {
    private static final Logger log = Logger.getLogger(FTPListService.class);

    private final FTPSession session;

    protected final Map<Command, ListService> implementations = new HashMap<Command, ListService>();

    public enum Command {
        stat(FTPCmd.STAT), mlsd(FTPCmd.MLSD), list(FTPCmd.LIST), lista(FTPCmd.LIST, "-a");

        private final FTPCmd command;
        private final String arg;

        Command(FTPCmd command) {
            this(command, null);
        }

        Command(FTPCmd command, String arg) {
            this.command = command;
            this.arg = arg;
        }

        public FTPCmd getCommand() {
            return command;
        }

        public String getArg() {
            return arg;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("Command{");
            sb.append("command=").append(command);
            sb.append(", arg='").append(arg).append('\'');
            sb.append('}');
            return sb.toString();
        }
    }

    public FTPListService(final FTPSession session, final String system, final TimeZone zone) {
        this(session, new DisabledPasswordStore(), new DisabledLoginCallback(), system, zone);
    }

    public FTPListService(final FTPSession session, final HostPasswordStore keychain, final LoginCallback prompt,
            final String system, final TimeZone zone) {
        this.session = session;
        // Directory listing parser depending on response for SYST command
        final CompositeFileEntryParser parser = new FTPParserSelector().getParser(system, zone);
        this.implementations.put(Command.list,
                new FTPDefaultListService(session, keychain, prompt, parser, Command.list));
        if (PreferencesFactory.get().getBoolean("ftp.command.stat")) {
            if (StringUtils.isNotBlank(system)) {
                if (!system.toUpperCase(Locale.ROOT).contains(FTPClientConfig.SYST_NT)) {
                    // Workaround for #5572.
                    this.implementations.put(Command.stat, new FTPStatListService(session, parser));
                }
            } else {
                this.implementations.put(Command.stat, new FTPStatListService(session, parser));
            }
        }
        if (PreferencesFactory.get().getBoolean("ftp.command.mlsd")) {
            this.implementations.put(Command.mlsd, new FTPMlsdListService(session, keychain, prompt));
        }
        if (PreferencesFactory.get().getBoolean("ftp.command.lista")) {
            this.implementations.put(Command.lista,
                    new FTPDefaultListService(session, keychain, prompt, parser, Command.lista));
        }
    }

    protected void remove(final Command command) {
        log.warn(String.format("Remove %s from listing strategies", command));
        implementations.remove(command);
    }

    @Override
    public AttributedList<Path> list(final Path directory, final ListProgressListener listener)
            throws BackgroundException {
        try {
            if (implementations.containsKey(Command.mlsd)) {
                // Note that there is no distinct FEAT output for MLSD. The presence of the MLST feature
                // indicates that both MLST and MLSD are supported.
                if (session.getClient().hasFeature(FTPCmd.MLST.getCommand())) {
                    try {
                        return this.post(directory, implementations.get(Command.mlsd).list(directory, listener),
                                listener);
                    } catch (InteroperabilityException | FTPInvalidListException e) {
                        this.remove(Command.mlsd);
                    }
                } else {
                    this.remove(Command.mlsd);
                }
            }
            if (implementations.containsKey(Command.stat)) {
                try {
                    return this.post(directory, implementations.get(Command.stat).list(directory, listener),
                            listener);
                } catch (FTPInvalidListException | InteroperabilityException e) {
                    this.remove(Command.stat);
                } catch (BackgroundException e) {
                    if (e.getCause() instanceof FTPException) {
                        log.warn(String.format("Command STAT failed with FTP error %s", e.getMessage()));
                    } else {
                        log.warn(String.format("Command STAT failed with I/O error %s", e.getMessage()));
                        new LoginConnectionService(new DisabledLoginCallback(), new DisabledHostKeyCallback(),
                                new DisabledPasswordStore(), listener).connect(session, PathCache.empty(),
                                        new DisabledCancelCallback());
                    }
                    this.remove(Command.stat);
                }
            }
            if (implementations.containsKey(Command.lista)) {
                try {
                    return this.post(directory, implementations.get(Command.lista).list(directory, listener),
                            listener);
                } catch (InteroperabilityException e) {
                    this.remove(Command.lista);
                } catch (FTPInvalidListException e) {
                    // Empty directory listing. #7737
                }
            }
            try {
                return this.post(directory, implementations.get(Command.list).list(directory, listener), listener);
            } catch (FTPInvalidListException f) {
                // Empty directory listing
                return this.post(directory, f.getParsed(), listener);
            }
        } catch (IOException e) {
            throw new FTPExceptionMappingService().map("Listing directory {0} failed", e, directory);
        }
    }

    /**
     * Handle all symbolic link targets
     */
    protected AttributedList<Path> post(final Path directory, final AttributedList<Path> list,
            final ListProgressListener listener) throws BackgroundException {
        try {
            final List<Path> verified = new ArrayList<Path>();
            final Set<Path> removal = new HashSet<>();
            for (final Path file : list) {
                if (file.isSymbolicLink()) {
                    final Path target = file.getSymlinkTarget();
                    if (session.getClient().changeWorkingDirectory(file.getAbsolute())) {
                        file.setType(EnumSet.of(Path.Type.directory, Path.Type.symboliclink));
                        target.setType(EnumSet.of(Path.Type.directory));
                    } else {
                        // Try if change working directory to symbolic link target succeeds
                        if (session.getClient().changeWorkingDirectory(target.getAbsolute())) {
                            // Workdir change succeeded
                            file.setType(EnumSet.of(Path.Type.directory, Path.Type.symboliclink));
                            target.setType(EnumSet.of(Path.Type.directory));
                        } else {
                            file.setType(EnumSet.of(Path.Type.file, Path.Type.symboliclink));
                            target.setType(EnumSet.of(Path.Type.file));
                        }
                    }
                    if (verified.add(file)) {
                        // Make sure we remove and add because hash code will change
                        removal.add(file);
                    }
                }
            }
            list.removeAll(removal);
            list.addAll(verified);
            listener.chunk(directory, list);
            return list;
        } catch (IOException e) {
            throw new FTPExceptionMappingService().map("Listing directory {0} failed", e, directory);
        }
    }
}