lib/raykit/command.rb



require 'open3'
require 'timeout'
require 'json'
require 'logger'

BUFFER_SIZE=1024 if(!defined?(BUFFER_SIZE))
module Raykit
    # Functionality to support executing and logging system commands

    class Command
        # The timeout in seconds, defaults to 0 if not specified

        attr_accessor :timeout
        # The working directory, defaults the current directory if not specified

        attr_accessor :directory
        # The start time

        attr_accessor :start_time
        # The execution time in seconds

        attr_accessor :elapsed
        attr_accessor :command,:output,:error,:exitstatus

        def init_defaults 
            @timeout=0
            @directory = Dir.pwd
            @output = ''
            @error = ''
            @exitstatus = 0
        end

        def initialize(command,timeout=0)
            init_defaults
            @command=command
            @timeout=timeout
            if(@command.length > 0)
                run
            end
            self
        end

        def run() 
            @start_time = Time.now
            timer = Timer.new
            if(@timeout == 0)
                @output,@error,process_status = Open3.capture3(@command) 
                @exitstatus=process_status.exitstatus
            else
                Open3.popen3(@command, :chdir=>@directory) { |stdin,stdout,stderr,thread|
                    tick=2
                    pid = thread.pid
                    start = Time.now
                    elapsed = Time.now-start
                    while (elapsed) < @timeout and thread.alive?
                        Kernel.select([stdout,stderr], nil, nil, tick)
                        begin
                            @output << stdout.read_nonblock(BUFFER_SIZE)
                            @error << stderr.read_nonblock(BUFFER_SIZE)
                        rescue IO::WaitReadable
                        rescue EOFError
                            @exitstatus=thread.value.exitstatus
                            break
                        end
                        elapsed = Time.now-start
                    end
                    if thread.alive?
                        if(Gem.win_platform?)
                            `taskkill /f /pid #{pid}`
                        else
                            Process.kill("TERM", pid)
                        end
                        @exitstatus=5
                    else
                        @exitstatus=thread.value.exitstatus
                    end
                  }
            end
            @elapsed = timer.elapsed
            if(@exitstatus == 0)
                LOG.log('Raykit.Command',Logger::Severity::INFO,JSON.generate(to_hash))
            else
                LOG.log('Raykit.Command',Logger::Severity::ERROR,JSON.generate(to_hash))
            end
        end

        def to_hash()
            hash = Hash.new
            hash[:command] = @command
            hash[:directory] = @directory
            hash[:timeout] = @timeout
            hash[:start_time] = @start_time
            hash[:elapsed] = @elapsed
            hash[:output] = @output
            hash[:error] = @error
            hash[:exitstatus] = @exitstatus
            hash
        end

        def from_hash(hash)
            @command = hash["command"]
            @directory=hash["directory"]
            @timeout=hash["timeout"]
            @start_time=hash["start_time"]
            @elapsed=hash["elapsed"]
            @output=hash["output"]
            @error=hash["error"]
            @exitstatus=hash["exitstatus"]
        end

        def self.parse(json)
            cmd=Command.new('')
            cmd.from_hash(JSON.parse(json))
            cmd
        end
    end
end