module Redcar class Project module Adapters module RemoteProtocols class SFTP < Protocol class << self def handle_error(e, host, user) return "Authentication failed for user #{user} in sftp://#{host}" if e.is_a?(Net::SSH::AuthenticationFailed) end end def connection require 'net/ssh' require 'net/sftp' Redcar.timeout(10) do @connection ||= Net::SSH.start(host, user, :password => password, :keys => private_key_files) end rescue OpenSSL::PKey::DSAError => error puts "*** Warning, DSA keys not supported." # Error with DSA key. Throw us back to a password input. Think this is because jopenssl bugs # out on valid dsa keys. raise Net::SSH::AuthenticationFailed, "DSA key-based authentication failed." rescue Redcar::TimeoutError raise "connection to #{host} timed out" end def touch(file) exec "touch \"#{escape(file)}\"" end def mkdir(new_dir_path) exec "mkdir -p \"#{escape(new_dir_path)}\"" end def mv(path, new_path) exec "mv \"#{escape(path)}\" \"#{escape(new_path)}\"" end def mtime(file) sftp_exec(:stat!, file).mtime end def download(remote, local) sftp_exec(:download!, remote, local) end def upload(local, remote) sftp_exec(:upload!, local, remote) end def delete(file) sftp_exec(:remove, file) end def use_cache? @use_cache end def dir_listing(path) if use_cache? if @cached_dirs[path] return @cached_dirs[path] end end return [] unless result = retrieve_dir_listing(path) process_dir_listing_response(result) end def retrieve_dir_listing(path) raise Adapters::Remote::PathDoesNotExist, "Path #{path} does not exist" unless check_folder(path) exec %Q( for file in #{path}/*; do test -f "$file" && echo "file|na|$file" test -d "$file" && test $(find $file -maxdepth 1 | wc -l) -eq 1 && echo "dir|0|$file" test -d "$file" && test $(find $file -maxdepth 1 | wc -l) -gt 1 && echo "dir|1|$file" done ) end def process_dir_listing_response(response) contents = [] response.each do |line| next unless line.include?('|') type, empty_flag, name = line.chomp.split('|') unless ['.', '..'].include?(name) hash = { :fullname => name, :name => File.basename(name), :type => type.to_sym } if type == "dir" hash[:empty] = (empty_flag == "0") end contents << hash end end contents end def exists?(path) is_file(path) or is_folder(path) end def is_folder(path) result = exec(%Q( test -d "#{path}" && echo y )) result =~ /^y/ ? true : false end def is_file(path) result = exec(%Q( test -f "#{path}" && echo y )) result =~ /^y/ ? true : false end def with_cached_directories(dirs) @cached_dirs = list_dirs(dirs) @use_cache = true yield ensure @use_cache = false end def list_dirs(dirs) cmd = "" dirs.each do |dir| cmd << <<-SH for file in #{dir}/*; do test -f "$file" && echo "file|na|$file" test -d "$file" && test $(find "$file" -maxdepth 1 | wc -l) -eq 1 && echo "dir|0|$file" test -d "$file" && test $(find "$file" -maxdepth 1 | wc -l) -gt 1 && echo "dir|1|$file" done SH end response = exec(cmd) listings = process_dir_listing_response(response) hash = {} listings.each do |listing| (hash[File.dirname(listing[:fullname])] ||= []) << listing end hash end private def escape(path) path.gsub("\\", "\\\\").gsub("\"", "\\\"") end def exec(what) puts "exec: #{what.inspect}" begin Redcar.timeout(10) do connection.exec!(what) end rescue Redcar::TimeoutError => e connection.shutdown! raise "#{host} connection timed out" end end def sftp_exec(method, *args) begin Redcar.timeout(10) do connection.sftp.send(method, *args) end rescue Redcar::TimeoutError connection.shutdown! raise "#{host} connection timed out" end end end end end end end