class GxG::Networking::SftpClient

Public Class Methods

new() click to toggle source
# File lib/gxg/net_clients.rb, line 4329
def initialize()
  @client = nil
  @user = nil
  self
end

Public Instance Methods

async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) click to toggle source
# File lib/gxg/net_clients.rb, line 4457
def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
  # progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
  # at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html.
  #
  # :requests - the number of pending SFTP requests to allow at any given time. When downloading an
  #  entire directory tree recursively, this will default to 16. Setting this higher might improve throughput.
  #  Reducing it will reduce throughput.
  #
  # :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
  # improve throughput. It defaults to 32,000 bytes.
  #
  result = false
  begin
    if @client
      unless the_remote_source.is_a?(::String)
        raise ArgumentError, "You MUST provide a local path as a String."
      end
      unless the_destination.is_a?(::String)
        raise ArgumentError, "You MUST provide a remote path as a String."
      end
      if progress_block.respond_to?(:call)
        channel = @client.download(the_remote_source, the_destination, options, &progress_block)
      else
        channel = @client.download(the_remote_source, the_destination, options)
      end
      channel.wait
      # True result means the request was sent, not that it completed.
      result = true
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
  end
  result
end
async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) click to toggle source
# File lib/gxg/net_clients.rb, line 4378
def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
  # options:
  #:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
  # below.
  #
  #:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory
  # tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve
  # throughput. Reducing it will reduce throughput.
  #
  #:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
  # improve throughput. It defaults to 32,000 bytes.
  #
  #:name - the filename to report to the progress monitor when an IO object is given as local. This defaults
  # to "<memory>".
  result = false
  begin
    if @client
      unless the_local_source.is_a?(::String)
        raise ArgumentError, "You MUST provide a local path as a String."
      end
      unless the_destination.is_a?(::String)
        raise ArgumentError, "You MUST provide a remote path as a String."
      end
      unless options[:name]
        options[:name] = File.basename(the_local_source)
      end
      if progress_block.respond_to?(:call)
        channel = @client.upload(the_local_source, the_destination, options, &progress_block)
      else
        channel = @client.upload(the_local_source, the_destination, options)
      end
      channel.wait
      # True result means the request was sent, not that it completed.
      result = true
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
  end
  result
end
change_ownership(the_path=nil, new_owners=nil, &block) click to toggle source
# File lib/gxg/net_clients.rb, line 4679
def change_ownership(the_path=nil, new_owners=nil, &block)
  result = false
  begin
    unless the_path.is_a?(::String)
      raise ArgumentError, "You MUST supply a remote path. (/path/to/something)."
    end
    unless new_owners.is_a?(::Hash)
      raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String."
    end
    unless new_owners[:user].is_a?(::String)
      raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String."
    end
    response = @client.setstat!(the_path, new_owners)
    if block.respond_to?(:call)
      block.call(response)
    end
    result = response.ok?
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:path => the_path, :new_owners => new_owners}})
  end
  result
end
change_permissions(the_path=nil, new_permissions=nil, &block) click to toggle source
# File lib/gxg/net_clients.rb, line 4702
def change_permissions(the_path=nil, new_permissions=nil, &block)
  result = false
  begin
    unless the_path.is_a?(::String)
      raise ArgumentError, "You MUST supply a remote path. (/path/to/something)."
    end
    unless new_permissions
      raise ArgumentError, "You MUST supply a new permission value. (GxG permission or Integer/Octal)."
    end
    if new_permissions.is_a?(::Hash)
      new_permissions = ::File.gxg_permissions_to_mode(new_permissions)
    end
    response = @client.setstat!(the_path, {:permissions => new_permissions})
    if block.respond_to?(:call)
      block.call(response)
    end
    result = response.ok?
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:path => the_path, :new_permissions => new_permissions}})
  end
  result
end
closed?() click to toggle source
# File lib/gxg/net_clients.rb, line 4362
def closed?()
  if @client
    @client.closed?
  else
    true
  end
end
download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) click to toggle source
# File lib/gxg/net_clients.rb, line 4492
def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
  # progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
  # at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html.
  #
  # :requests - the number of pending SFTP requests to allow at any given time. When downloading an
  #  entire directory tree recursively, this will default to 16. Setting this higher might improve throughput.
  #  Reducing it will reduce throughput.
  #
  # :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
  # improve throughput. It defaults to 32,000 bytes.
  #
  result = false
  begin
    if @client
      unless the_remote_source.is_a?(::String)
        raise ArgumentError, "You MUST provide a remote path as a String."
      end
      unless the_destination.is_a?(::String)
        raise ArgumentError, "You MUST provide a local path (or file name) as a String."
      end
      if progress_block.respond_to?(:call)
        result = @client.download!(the_remote_source, the_destination, options, &progress_block)
      else
        result = @client.download!(the_remote_source, the_destination, options)
      end
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
  end
  result
end
entries(the_path=nil, pattern=nil, flags=0, &block) click to toggle source

Remote File System Operations and Objects

# File lib/gxg/net_clients.rb, line 4524
def entries(the_path=nil, pattern=nil, flags=0, &block)
  # List entries in a given remote path.
  # If block is provided, resultant list is iterated over the block - (each_entry).
  result = []
  begin
    if @client
      # Validate the_path: /.*(?:\\|\/)(.+)$/ from: https://www.experts-exchange.com/questions/22088316/Extract-Filename-from-PATH-using-REGEX.html
      unless (/.*(?:\\|\/)(.+)$/.match(the_path.to_s) || the_path.to_s.split("/").size > 0 || the_path == "/")
        raise ArgumentError, "You MUST provide a valid remote file system path."
      end
      if pattern.is_a?(::String)
        # use glob
        unless flags.is_a?(::Numeric)
          flags = 0
        end
        raw_list = @client.dir.glob(the_path, pattern, flags)
      else
        # use entries
        raw_list = @client.dir.entries(the_path)
      end
      list = []
      raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
      blank_permissions = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
      #
      raw_list.each do |details|
        #
        unless details.name == "." || details.name == ".."
          #
          record = {:title => (details.name), :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil}
          permission_record = blank_permissions.clone
          #
          record = record.merge({:size => (details.attributes.size), :accessed => (Time.at(details.attributes.atime)).to_datetime, :modified => (Time.at(details.attributes.mtime)).to_datetime, :state_modified => (Time.at(details.attributes.mtime)).to_datetime, :user_id => (details.attributes.uid), :group_id => (details.attributes.gid)})
          if details.directory?()
            record[:type] = :directory
            record[:owner_type] = :directory
          end
          if details.file?()
            record[:type] = :file
            record[:owner_type] = :file
          end
          if details.symlink?()
            record[:type] = :symlink
            record[:owner_type] = :symlink
          end
          # puts "Got: #{details.attributes.permissions.inspect}"
          record[:permissions] = ::File.mode_permission_to_gxg(details.attributes.permissions.to_i)
          current_groups = @client.session.exec!("groups").gsub("\n","").split(" ")
          if current_groups.size > 0
            effective_permissions = raw_permission.clone
            if @user == details.attributes.owner
              include_owner = true
            else
              include_owner = false
            end
            if current_groups.include?(details.attributes.group)
              include_group = true
            else
              include_group = false
            end
            #
            permissions_stack = []
            if include_owner
              permissions_stack << record[:permissions][:owner]
            end
            if include_group
              permissions_stack << record[:permissions][:group]
            end
            permissions_stack << record[:permissions][:other]
            permissions_stack.each do |entry|
              entry.keys do |the_key|
                if entry[(the_key)] == true
                  effective_permissions[(the_key)] = true
                end
              end
            end
            record[:permissions][:effective] = effective_permissions
            if record[:permissions][:effective][:read]
              record[:flags] << :read
            end
            if record[:permissions][:effective][:write]
              record[:flags] << :write
            end
          end
          list << record
        end
        #
      end
      #
      if block.respond_to?(:call)
        list.each do |entry|
          block.call(entry)
        end
      end
      result = list
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:remote_path => the_path}})
  end
  result
end
login(the_url=nil, options={}) click to toggle source
# File lib/gxg/net_clients.rb, line 4335
def login(the_url=nil, options={})
  result = false
  begin
    unless the_url.is_a?(::URI::Generic)
      raise ArgumentError, "You MUST provide a valid URI"
    end
    @user = the_url.user
    if the_url.password
      options[:password] = the_url.password
    end
    if the_url.port
      options[:port] = the_url.port
    end
    @client = ::Net::SFTP.start(the_url.hostname, the_url.user, options)
    @client.connect!
    if @client.closed?
      @client = nil
      raise Exception, "Failed to open a connection."
    else
      result = true
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
  end
  result
end
logout() click to toggle source
# File lib/gxg/net_clients.rb, line 4370
def logout()
  if @client
    @client.close_channel
    @client = nil
    true
  end
end
make_directory(the_path=nil, &block) click to toggle source
# File lib/gxg/net_clients.rb, line 4785
def make_directory(the_path=nil, &block)
  result = false
  begin
    unless the_path.is_a?(::String)
      raise ArgumentError, "You MUST supply a new directory path. (/path/to/new_dir)."
    end
    response = @client.mkdir!(the_path)
    if block.respond_to?(:call)
      block.call(response)
    end
    result = response.ok?
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
  end
  result
end
open_remote_file(the_path=nil, permissions={}) click to toggle source
# File lib/gxg/net_clients.rb, line 4625
def open_remote_file(the_path=nil, permissions={})
  result = nil
  begin
    if @client
      filename = ::File.basename(the_path)
      entries = this().entries(::File.dirname(the_path))
      open_only = false
      flags = []
      entries.each do |item|
        if item[:title] == filename
          open_only = true
          flags = item[:flags]
          break
        end
      end
      if open_only
        if flags.include?(:read)
          if flags.include?(:write)
            open_mode = "w+"
          else
            open_mode = "r"
          end
        else
          if flags.include?(:write) 
            open_mode = "w"
          else                
            open_mode = "r"
          end
        end
        #open
        result = @client.file.open(the_path.to_s, open_mode)
      else
        #create
        if permissions.is_a?(::Hash)
          if permissions[:owner].is_a?(::Hash) && permissions[:group].is_a?(::Hash) && permissions[:other].is_a?(::Hash)
            permissions = ::File.gxg_permissions_to_mode(permissions)
          end
        end
        if permissions.is_a?(::Numeric)
          unless permissions > 0
            permissions = 0644.to_i
          end
        else
          permissions = 0644.to_i
        end
        result = @client.file.open(the_path.to_s, "w+", permissions)
      end
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:remote_path => the_path, :permissions => permissions}})
  end
  result
end
remove_directory(the_path=nil, &block) click to toggle source
# File lib/gxg/net_clients.rb, line 4802
def remove_directory(the_path=nil, &block)
  result = false
  begin
    unless the_path.is_a?(::String)
      raise ArgumentError, "You MUST supply a directory path to remove. (/path/to/dir)."
    end
    response = @client.rmdir!(the_path)
    if block.respond_to?(:call)
      block.call(response)
    end
    result = response.ok?
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
  end
  result
end
remove_remote_file(the_path=nil, &block) click to toggle source
# File lib/gxg/net_clients.rb, line 4725
def remove_remote_file(the_path=nil, &block)
  result = false
  begin
    unless the_path.is_a?(::String)
      raise ArgumentError, "You MUST supply a remote file path. (/path/to/file)."
    end
    response = @client.remove!(the_path)
    if block.respond_to?(:call)
      block.call(response)
    end
    result = response.ok?
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:path => the_path}})
  end
  result
end
rename(name=nil, new_name=nil,&block) click to toggle source
# File lib/gxg/net_clients.rb, line 4765
def rename(name=nil, new_name=nil,&block)
  result = false
  begin
    unless name.is_a?(::String)
      raise ArgumentError, "You MUST supply a remote file path. (/path/to/old_name)."
    end
    unless new_name.is_a?(::String)
      raise ArgumentError, "You MUST supply a new file name path. (/path/to/new_name)."
    end
    response = @client.rename!(name,new_name,0x0004)
    if block.respond_to?(:call)
      block.call(response)
    end
    result = response.ok?
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
  end
  result
end
upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) click to toggle source
# File lib/gxg/net_clients.rb, line 4419
def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
  # options:
  #:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
  # below.
  #
  #:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory
  # tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve
  # throughput. Reducing it will reduce throughput.
  #
  #:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
  # improve throughput. It defaults to 32,000 bytes.
  #
  #:name - the filename to report to the progress monitor when an IO object is given as local. This defaults
  # to "<memory>".
  result = false
  begin
    if @client
      unless the_local_source.is_a?(::String)
        raise ArgumentError, "You MUST provide a local path as a String."
      end
      unless the_destination.is_a?(::String)
        raise ArgumentError, "You MUST provide a remote path as a String."
      end
      unless options[:name]
        options[:name] = File.basename(the_local_source)
      end
      if progress_block.respond_to?(:call)
        result = @client.upload!(the_local_source, the_destination, options, &progress_block)
      else
        result = @client.upload!(the_local_source, the_destination, options)
      end
    end
  rescue Exception => the_error
    log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
  end
  result
end