lib/ethon/multies/operations.rb



module Ethon
  module Multies # :nodoc

    # This module contains logic to run a multi.
    module Operations

      # Initialize variables.
      #
      # @example Initialize variables.
      #   multi.init_vars
      def init_vars
        @timeout = ::FFI::MemoryPointer.new(:long)
        @timeval = Curl::Timeval.new
        @fd_read = Curl::FDSet.new
        @fd_write = Curl::FDSet.new
        @fd_excep = Curl::FDSet.new
        @max_fd = ::FFI::MemoryPointer.new(:int)
      end

      # Return wether the multi still requests or not.
      #
      # @example Return if ongoing.
      #   multi.ongoing?
      #
      # @return [ Boolean ] True if ongoing, else false.
      def ongoing?
        easy_handles.size > 0 || (!defined?(@running_count) || running_count > 0)
      end

      # Perform multi.
      #
      # @example Perform multi.
      #   multi.perform
      def perform
        Ethon.logger.debug("ETHON: started MULTI") if Ethon.logger
        while ongoing?
          run
          timeout = get_timeout
          next if timeout == 0
          reset_fds
          set_fds(timeout)
        end
        Ethon.logger.debug("ETHON: performed MULTI") if Ethon.logger
        nil
      end

      # Get timeout.
      #
      # @example Get timeout.
      #   multi.get_timeout
      #
      # @raise [Ethon::Errors::MultiTimeout] when getting the timeout failed.
      def get_timeout
        code = Curl.multi_timeout(handle, @timeout)
        raise Errors::MultiTimeout.new(code) unless code == :ok
        timeout = @timeout.read_long
        timeout = 1 if timeout < 0
        timeout
      end

      # Reset file describtors.
      #
      # @example Reset fds.
      #   multi.reset_fds
      def reset_fds
        @fd_read.clear
        @fd_write.clear
        @fd_excep.clear
      end

      # Set fds.
      #
      # @example Set fds.
      #   multi.set_fds
      #
      # @raise [Ethon::Errors::MultiFdset] when setting the file descriptors failed.
      # @raise [Ethon::Errors::Select] when select failed.
      def set_fds(timeout)
        code = Curl.multi_fdset(handle, @fd_read, @fd_write, @fd_excep, @max_fd)
        raise Errors::MultiFdset.new(code) unless code == :ok
        max_fd = @max_fd.read_int
        if max_fd == -1
          sleep(0.001)
        else
          @timeval[:sec] = timeout / 1000
          @timeval[:usec] = (timeout * 1000) % 1000000
          code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval)
          raise Errors::Select.new(::FFI.errno) if code < 0
        end
      end

      # Check.
      #
      # @example Check.
      #   multi.check
      def check
        msgs_left = ::FFI::MemoryPointer.new(:int)
        while true
          msg = Curl.multi_info_read(handle, msgs_left)
          break if msg.null?
          next if msg[:code] != :done
          easy = easy_handles.find{ |e| e.handle == msg[:easy_handle] }
          easy.return_code = msg[:data][:code]
          delete(easy)
          easy.complete
          Ethon.logger.debug("ETHON:         performed #{easy.log_inspect}") if Ethon.logger
        end
      end

      # Run.
      #
      # @example Run
      #   multi.run
      def run
        begin code = trigger end while code == :call_multi_perform
        check
      end

      # Trigger.
      #
      # @example Trigger.
      #   multi.trigger
      def trigger
        running_count = FFI::MemoryPointer.new(:int)
        code = Curl.multi_perform(handle, running_count)
        @running_count = running_count.read_int
        code
      end

      # Return number of running requests.
      #
      # @example Return count.
      #   multi.running_count
      #
      # @return [ Integer ] Number running requests.
      def running_count
        @running_count ||= nil
      end
    end
  end
end