module Net::SCP::Upload

def next_item_state(channel)

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

def preserve_attributes_if_requested(channel)

#upload_directory_state, depending on what is being uploaded.
server to respond before proceeding to either #upload_file_state or
If the :preserve option is set, send a 'T' directive and wait for the
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

def send_data_state(channel)

Otherwise, sends a 0-byte and transfers to #next_item_state.
If any data remains to be transferred from the current file, sends it.
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

def set_current(channel, path)

Sets the given +path+ as the new current item to upload.
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

def upload_current_state(channel)

to #upload_directory_state.
item is a file, goes to #upload_file_state. If it is a directory, goes
Determines what the next thing to upload is, and branches. If the next
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

def upload_directory_state(channel)

awaites the server's 0-byte response. Then goes to #next_item_state.
After transferring attributes (if requested), sends a 'D' directive and
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

def upload_file_state(channel)

awaits the server's 0-byte response. Then goes to #send_data_state.
After transferring attributes (if requested), sends a 'C' directive and
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

def upload_start_state(channel)

sets the current item to upload, and jumps to #upload_current_state.
The start state for uploads. Simply sets up the upload scaffolding,
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