# 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

make_link(new_link_path=nil, existing_path=nil, symlink=true, &block) click to toggle source

    # File lib/gxg/net_clients.rb, line 4742
    def make_link(new_link_path=nil, existing_path=nil, symlink=true, &block)
      result = false
      begin
        unless new_link_path.is_a?(::String)
          raise ArgumentError, "You MUST supply a new link path. (/path/to/new_link)."
        end
        unless existing_path.is_a?(::String)
          raise ArgumentError, "You MUST supply an existing file path. (/path/to/file)."
        end
        unless symlink
          symlink = true
        end
        response = @client.link!(new_link_path,existing_path,symlink)
        if block.respond_to?(:call)
          block.call(response)
        end
        result = response.ok?
      rescue Exception => the_error
        log_error({:error => the_error, :parameters => {:new_link_path => new_link_path, :existing_path => existing_path, :symlink => symlink}})
      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