module Net::SCP::Download
def download_start_state(channel)
readiness to proceed. Then, the state machine is placed into the
and scaffolding and then sends a 0-byte to the remote server, indicating
time the channel is processed. This state does some basic error checking
#start_command method puts the state machine into this state the first
This is the starting state for the download state machine. The
def download_start_state(channel) if channel[:local].respond_to?(:write) && channel[:options][:recursive] raise Net::SCP::Error, "cannot recursively download to an in-memory location" elsif channel[:local].respond_to?(:write) && channel[:options][:preserve] lwarn { ":preserve option is ignored when downloading to an in-memory buffer" } channel[:options].delete(:preserve) elsif channel[:options][:recursive] && !File.exist?(channel[:local]) Dir.mkdir(channel[:local]) end channel.send_data("\0") channel[:state] = :read_directive end
def finish_read_state(channel)
#read_directive_state (for recursive downloads). A 0-byte is sent to the
jumps to either #finish_state (for single-file downloads) or
Finishes off the read, sets the times for the file (if any), and then
def finish_read_state(channel) channel[:io].close unless channel[:io] == channel[:local] if channel[:options][:preserve] && channel[:file][:times] File.utime(channel[:file][:times][:atime], channel[:file][:times][:mtime], channel[:file][:name]) end channel[:file] = nil channel[:state] = channel[:stack].empty? ? :finish : :read_directive channel.send_data("\0") end
def parse_directive(text)
type of directive it is. The hash may also contain other, directive-specific
then returns a hash with at least one key, :type, which describes what
Parses the given +text+ to extract which SCP directive it contains. It
def parse_directive(text) case type = text[0] when "\x00" # Success { :type => :OK } when "\x01" { :type => :warning, :message => text[1..-1] } when "\x02" { :type => :error, :message => text[1..-1] } when ?T parts = text[1..-1].split(/ /, 4).map { |i| i.to_i } { :type => :times, :mtime => Time.at(parts[0], parts[1]), :atime => Time.at(parts[2], parts[3]) } when ?C, ?D parts = text[1..-1].split(/ /, 3) { :type => (type == ?C ? :file : :directory), :mode => parts[0].to_i(8), :size => parts[1].to_i, :name => parts[2].chomp } when ?E { :type => :end } else raise ArgumentError, "unknown directive: #{text.inspect}" end end
def read_data_state(channel)
be read. As soon as there is no more data to read for the current file,
Reads data from the channel for as long as there is data remaining to
def read_data_state(channel) return if channel[:buffer].empty? data = channel[:buffer].read!(channel[:remaining]) channel[:io].write(data) channel[:remaining] -= data.length progress_callback(channel, channel[:file][:name], channel[:file][:size] - channel[:remaining], channel[:file][:size]) await_response(channel, :finish_read) if channel[:remaining] <= 0 end
def read_directive_state(channel)
directive. (See the SCP protocol documentation in Net::SCP for the
This state parses the next full line (up to a new-line) for the next
def read_directive_state(channel) return unless line = channel[:buffer].read_to("\n") channel[:buffer].consume! directive = parse_directive(line) case directive[:type] when :OK return when :warning channel[:error_string] << directive[:message] when :error channel[:error_string] << directive[:message] when :times channel[:times] = directive when :directory read_directory(channel, directive) when :file read_file(channel, directive) when :end channel[:local] = File.dirname(channel[:local]) channel[:stack].pop channel[:state] = :finish if channel[:stack].empty? end channel.send_data("\0") end
def read_directory(channel, directive)
Sets the new directory as the current directory, creates the directory
def read_directory(channel, directive) if !channel[:options][:recursive] raise Net::SCP::Error, ":recursive not specified for directory download" end channel[:local] = File.join(channel[:local], directive[:name]) if File.exist?(channel[:local]) && !File.directory?(channel[:local]) raise Net::SCP::Error, "#{channel[:local]} already exists and is not a directory" elsif !File.exist?(channel[:local]) Dir.mkdir(channel[:local], directive[:mode] | 0700) end if channel[:options][:preserve] && channel[:times] File.utime(channel[:times][:atime], channel[:times][:mtime], channel[:local]) end channel[:stack] << directive channel[:times] = nil end
def read_file(channel, directive)
Opens the given file locally, and switches to #read_data_state to do the
def read_file(channel, directive) if !channel[:local].respond_to?(:write) directive[:name] = (channel[:options][:recursive] || File.directory?(channel[:local])) ? File.join(channel[:local], directive[:name]) : channel[:local] end channel[:file] = directive.merge(:times => channel[:times]) channel[:io] = channel[:local].respond_to?(:write) ? channel[:local] : File.new(directive[:name], "wb", directive[:mode] | 0600) channel[:times] = nil channel[:remaining] = channel[:file][:size] channel[:state] = :read_data progress_callback(channel, channel[:file][:name], 0, channel[:file][:size]) end