class IOP::FTPFileReader

Feed class to read file from FTP server.

This class an adapter for the standard Ruby Net::FTP class.

### Use case: retrieve file from FTP server and store it locally.

require 'iop/file'
require 'iop/net/ftp'
( IOP::FTPFileReader.new('ftp.gnu.org', '/pub/README') | IOP::FileWriter.new('README') ).process!

@since 0.1

Public Class Methods

new(ftp, file, size: nil, offset: nil, block_size: DEFAULT_BLOCK_SIZE, **options) click to toggle source

Creates class instance.

@param ftp [String, Net::FTP] FTP server to connect to

@param file [String] file name to process

@param size [Integer] total number of bytes to read; nil value instructs to read until end-of-data is reached

@param offset [Integer] offset in bytes from the stream start to seek to; nil value means no seeking is performed

@param block_size [Integer] size of blocks to process data with

@param options [Hash] extra keyword parameters passed to Net::FTP constructor

ftp can be either a String of Net::FTP instance. If it is a string a corresponding Net::FTP instance will be created with options passed to its constructor.

If ftp is a string, a created FTP connection is managed, e.g. it is closed after the process is complete, otherwise supplied object is left as is and no closing is performed. This allows to reuse FTP connection for a sequence of operations.

Refer to Net::FTP documentation for available options.

# File lib/iop/net/ftp.rb, line 89
def initialize(ftp, file, size: nil, offset: nil, block_size: DEFAULT_BLOCK_SIZE, **options)
  @block_size = size.nil? ? block_size : IOP.min(size, block_size)
  @left = @size = size
  @options = options
  @offset = offset
  @file = file
  @ftp = ftp
end

Public Instance Methods

process!() click to toggle source
# File lib/iop/net/ftp.rb, line 98
def process!
  setup
  begin
    # FTP logic taken from Net::FTP#retrbinary
    @io = transfercmd('RETR ' << @file, @offset)
    begin
      loop do
        read_size = @size.nil? ? @block_size : IOP.min(@left, @block_size)
        break if read_size.zero?
        data = @io.read(read_size)
        if data.nil?
          if @size.nil?
            break
          else
            raise EOFError, INSUFFICIENT_DATA
          end
        else
          unless @left.nil?
            @left -= data.size
            raise IOError, EXTRA_DATA if @left < 0
          end
          process(data) unless data.size.zero?
        end
      end
      process
      @io.shutdown(Socket::SHUT_WR)
      @io.read_timeout = 1
      @io.read
    ensure
      @io.close
    end
    voidresp
  ensure
    cleanup
  end
end