module Photocopier
  class FTP < Adapter
    def initialize(options = {})
      @options = options
    end

    def options
      @options.clone
    end

    def get(remote_path, file_path = nil)
      session.get remote_path, file_path
    end

    def put_file(file_path, remote_path)
      session.put_file file_path, remote_path
    end

    def delete(remote_path)
      session.delete(remote_path)
    end

    def get_directory(remote_path, local_path, exclude = [])
      FileUtils.mkdir_p(local_path)
      lftp(local_path, remote_path, false, exclude, options[:port])
    end

    def put_directory(local_path, remote_path, exclude = [])
      lftp(local_path, remote_path, true, exclude, options[:port])
    end

    def inferred_port
      if options[:port].nil? && options[:scheme] == 'sftp'
        22
      elsif options[:port].nil?
        21
      else
        options[:port]
      end
    end

    private

    def session
      @session ||= Session.new(options)
    end

    def lftp(local, remote, reverse, exclude, port = nil)
      remote = Shellwords.escape(remote)
      local = Shellwords.escape(local)
      command = [
        "set ftp:list-options -a",
        "set cmd:fail-exit true",
        "open -p #{port || inferred_port} #{remote_ftp_url}",
        "find -d 1 #{remote} || mkdir -p #{remote}",
        "lcd #{local}",
        "cd #{remote}",
        lftp_mirror_arguments(reverse, exclude)
      ].join("; ")

      run "lftp -c '#{command}'"
    end

    def remote_ftp_url
      url = options[:scheme].dup.presence || "ftp"
      url << "://"
      if options[:user].present?
        url << CGI.escape(options[:user])
        url << ":#{CGI.escape(options[:password])}" if options[:password].present?
        url << "@"
      end
      url << options[:host]
      url
    end

    def lftp_mirror_arguments(reverse, exclude = [])
      mirror = "mirror --delete --use-cache --verbose" \
               " --no-perms --allow-suid --no-umask --parallel=5"
      mirror << " --reverse --dereference" if reverse
      exclude.each do |glob|
        mirror << " --exclude-glob #{glob}" # NOTE do not use Shellwords.escape here
      end
      mirror
    end
  end
end