module Net::SCP::Upload

This module implements the state machine for uploading information to a remote server. It exposes no public methods. See Net::SCP#upload for a discussion of how to use Net::SCP to upload data.

Constants

DEFAULT_CHUNK_SIZE

The default read chunk size, if an explicit chunk-size is not specified by the client.

Private Instance Methods

next_item_state(channel) click to toggle source

Checks the work queue to see what needs to be done next. If there is nothing to do, calls Net::SCP#finish_state. If we're at the end of a directory, sends an 'E' directive and waits for the server to respond before moving to next_item_state. Otherwise, sets the next thing to upload and moves to upload_current_state.

# File lib/net/scp/upload.rb, line 92
def next_item_state(channel)
  if channel[:stack].empty?
    finish_state(channel)
  else
    next_item = channel[:stack].last.shift
    if next_item.nil?
      channel[:stack].pop
      channel[:cwd] = File.dirname(channel[:cwd])
      channel.send_data("E\n")
      await_response(channel, channel[:stack].empty? ? :finish : :next_item)
    else
      set_current(channel, next_item)
      upload_current_state(channel)
    end
  end
end
preserve_attributes_if_requested(channel) click to toggle source

If the :preserve option is set, send a 'T' directive and wait for the server to respond before proceeding to either upload_file_state or upload_directory_state, depending on what is being uploaded.

# File lib/net/scp/upload.rb, line 126
def preserve_attributes_if_requested(channel)
  if channel[:options][:preserve] && !channel[:preserved]
    channel[:preserved] = true
    stat = channel[:stat]
    directive = "T%d %d %d %d\n" % [stat.mtime.to_i, stat.mtime.usec, stat.atime.to_i, stat.atime.usec]
    channel.send_data(directive)
    type = stat.directory? ? :directory : :file
    await_response(channel, "upload_#{type}")
    return false
  else
    channel[:preserved] = false
    return true
  end
end
send_data_state(channel) click to toggle source

If any data remains to be transferred from the current file, sends it. Otherwise, sends a 0-byte and transfers to next_item_state.

# File lib/net/scp/upload.rb, line 74
def send_data_state(channel)
  data = channel[:io].read(channel[:chunk_size])
  if data.nil?
    channel[:io].close unless channel[:local].respond_to?(:read)
    channel.send_data("\0")
    await_response(channel, :next_item)
  else
    channel[:sent] += data.length
    progress_callback(channel, channel[:name], channel[:sent], channel[:size])
    channel.send_data(data)
  end
end
set_current(channel, path) click to toggle source

Sets the given path as the new current item to upload.

# File lib/net/scp/upload.rb, line 110
def set_current(channel, path)
  path = channel[:cwd] ? File.join(channel[:cwd], path) : path
  channel[:current] = path

  if channel[:current].respond_to?(:read)
    channel[:stat] = channel[:current].stat if channel[:current].respond_to?(:stat)
  else
    channel[:stat] = File.stat(channel[:current])
  end

  channel[:size] = channel[:stat] ? channel[:stat].size : channel[:current].size
end
upload_current_state(channel) click to toggle source

Determines what the next thing to upload is, and branches. If the next item is a file, goes to upload_file_state. If it is a directory, goes to upload_directory_state.

# File lib/net/scp/upload.rb, line 31
def upload_current_state(channel)
  if channel[:current].respond_to?(:read)
    upload_file_state(channel)
  elsif File.directory?(channel[:current])
    raise Net::SCP::Error, "can't upload directories unless :recursive" unless channel[:options][:recursive]
    upload_directory_state(channel)
  elsif File.file?(channel[:current])
    upload_file_state(channel)
  else
    raise Net::SCP::Error, "not a directory or a regular file: #{channel[:current].inspect}"
  end
end
upload_directory_state(channel) click to toggle source

After transferring attributes (if requested), sends a 'D' directive and awaites the server's 0-byte response. Then goes to next_item_state.

# File lib/net/scp/upload.rb, line 46
def upload_directory_state(channel)
  if preserve_attributes_if_requested(channel)
    mode = channel[:stat].mode & 07777
    directive = "D%04o %d %s\n" % [mode, 0, File.basename(channel[:current])]
    channel.send_data(directive)
    channel[:cwd] = channel[:current]
    channel[:stack] << Dir.entries(channel[:current]).reject { |i| i == "." || i == ".." }
    await_response(channel, :next_item)
  end
end
upload_file_state(channel) click to toggle source

After transferring attributes (if requested), sends a 'C' directive and awaits the server's 0-byte response. Then goes to send_data_state.

# File lib/net/scp/upload.rb, line 59
def upload_file_state(channel)
  if preserve_attributes_if_requested(channel)
    mode = channel[:stat] ? channel[:stat].mode & 07777 : channel[:options][:mode]
    channel[:name] = channel[:current].respond_to?(:read) ? channel[:remote] : channel[:current]
    directive = "C%04o %d %s\n" % [mode || 0640, channel[:size], File.basename(channel[:name])]
    channel.send_data(directive)
    channel[:io] = channel[:current].respond_to?(:read) ? channel[:current] : File.open(channel[:current], "rb")
    channel[:sent] = 0
    progress_callback(channel, channel[:name], channel[:sent], channel[:size])
    await_response(channel, :send_data)
  end
end
upload_start_state(channel) click to toggle source

The start state for uploads. Simply sets up the upload scaffolding, sets the current item to upload, and jumps to upload_current_state.

# File lib/net/scp/upload.rb, line 17
def upload_start_state(channel)
  if channel[:local].respond_to?(:read)
    channel[:options].delete(:recursive)
    channel[:options].delete(:preserve)
  end

  channel[:chunk_size] = channel[:options][:chunk_size] || DEFAULT_CHUNK_SIZE
  set_current(channel, channel[:local])
  await_response(channel, :upload_current)
end