class Exedb

Database-like interface for long-running tasks.

Each instance can run single command (get). If two or more instances with the same command do 'get', then only first will really execute command, all others will wait for result (flock is used).

Results of command execution (stdout) is cached. Next 'get' will return cached results (unless cache timeout happened).

You can force execution, calling 'update' method.

Exit code is available via 'code' method

Example:

d=Exedb.new("ls -la .")
d.cache_timeout=5  # 5 seconds for cache expire
list=d.get    # execute 'ls' and get list of files
list=d.get    # just get list again (cached)
sleep 5
list=d.get    # execute 'ls' again and get result
list=d.update # force 'ls' execution!
d.line_transform {|l|
  if l =~ /^d.*(\S+)$/
     return "DIR: $1"   # not correct, do not use in production :)
  elsif l =~ /^-.*(\S+)$/
     return "FILE: $1"  # just transform example
  else
     return nil         # skip this line
  end
}
d.update   # get list of directories and files in special format

Constants

DEF_CACHE_TIMEOUT
DEF_DIR
SLEEP_TIME
VERSION

Attributes

cache_dir[RW]

directory for cache files

cache_timeout[RW]

default cache timeout

update_time[R]

last cache update time

Public Class Methods

new(str='') click to toggle source

Constructor @str - command to be executed

# File lib/exedb.rb, line 54
def initialize(str='')
  @update_time=Time.parse("1970-01-01")
  @cache_timeout=DEF_CACHE_TIMEOUT
  @cache_dir=DEF_DIR
  @code=-1
  Dir.mkdir DEF_DIR unless File.directory? DEF_DIR
  self.update_method=(str)
end

Public Instance Methods

all_transform(&block) click to toggle source

transform all command output at end of execution block is called with parameters: content, return code returned content replaces original output

Example:

@d.all_transform {|str,code|
   "Total: #{str.lines.count} lines\nExit code: #{code}"
}
# File lib/exedb.rb, line 148
def all_transform(&block)
  if block
    obj = Object.new
    obj.define_singleton_method(:_, &block)
    @alltransform=obj.method(:_).to_proc
  else
    @alltransform=nil
  end
end
cache_state() click to toggle source

Returns symbol of cache state:

  • :updated = actual

  • :need_update = new command execution needed

  • :need_reread = just cache file reread is neede

# File lib/exedb.rb, line 216
def cache_state
  if File.exists? @path
    mtime=File.mtime(@path)
    return :need_update if mtime+@cache_timeout<Time.now
    return :need_reread if @update_time<mtime
    return :updated
  end
  :need_update
end
code() click to toggle source

Get last execution return code. NOTE!!! It is also cached, even on error.

# File lib/exedb.rb, line 193
def code
  actualize
  @code
end
get() click to toggle source

Get last execution result (stdout), or start new command execution and return result if cache is invalid.

# File lib/exedb.rb, line 184
def get
  actualize
  @content
end
line_transform(&block) click to toggle source

transform each line in command output if nil is returned, line is skipped

Example:

@d.line_transform {|str|
   str.downcase
}
# File lib/exedb.rb, line 122
def line_transform(&block)
  if block
    obj = Object.new
    obj.define_singleton_method(:_, &block)
    @transform=obj.method(:_).to_proc
  else
    @transform=nil
  end
end
no_all_transform() click to toggle source

cancel transformation of full output

# File lib/exedb.rb, line 161
def no_all_transform
  @alltransform=nil
end
no_line_transform() click to toggle source

cancel transformation each line

# File lib/exedb.rb, line 135
def no_line_transform
  @transform=nil    
end
peek() click to toggle source

Do not execute command - just peek in cache file… Usefull for intermediate command output peeking

# File lib/exedb.rb, line 202
def peek
  begin
    File.read(@path)      
  rescue
    ''
  end
end
put(str) click to toggle source

Just alias for update_method=

# File lib/exedb.rb, line 175
def put(str)
  self.update_method=(str)
end
update() click to toggle source

Force command execution. If another instance with the same command is in progress, no new execution will be started

# File lib/exedb.rb, line 66
def update
  @content=''

  # create file if needed
  unless File.file?(@path)
    File.open(@path,File::RDWR|File::CREAT,0644){|f|}
  end

  File.open(@path, "r+:UTF-8") { |file|
    if file.flock(File::LOCK_EX|File::LOCK_NB)
      begin
        IO.popen(@update_method){|pipe|
          line=pipe.gets
          while line
            line=@transform.call(line) if @transform
            if line
              file.puts line
              file.flush
              @content = @content+line
            end
            line=pipe.gets
          end
        }
        @code=$?.exitstatus
      rescue
        @content=''
        @code=-1
      end
      if @alltransform
        @content=@alltransform.call(@content,@code)
        file.seek(0,IO::SEEK_SET)
        file.write @content
        file.truncate(@content.size)
        file.flush
      end
      File.open("#{@path}.code",File::RDWR|File::CREAT, 0644){|code_file|
        code_file.puts @code
      }
      file.flock(File::LOCK_UN)
    else
      read_cache
    end
    @update_time=Time.now
  }
  #!!!warn "UPDATED!'"
  @content
end
update_in_progress?() click to toggle source
# File lib/exedb.rb, line 226
def update_in_progress?
  if File.exists? @path
    File.open(@path, File::RDONLY) { |file|
      if file.flock(File::LOCK_EX|File::LOCK_NB)
        file.flock(File::LOCK_UN)
        return false
      end
    }
    return true
  end
  return false
end
update_method=(str) click to toggle source

Replace executing command

# File lib/exedb.rb, line 167
  def update_method=(str)
    @update_method=str
    @key=generate_key str
    @path=File.join(DEF_DIR, @key)
#    warn "key=#{@key}; path=#{@path}; u=#{str}"
  end

Protected Instance Methods

actualize() click to toggle source
# File lib/exedb.rb, line 241
def actualize
  case cache_state
  when :need_update
    update
  when :need_reread
    read_cache
  end
end
generate_key(u) click to toggle source
# File lib/exedb.rb, line 250
def generate_key u
  f=u.tr('^qwertyuiopasdfghjklzxcvbnm_-','')
  d=Digest::SHA256.hexdigest(u)
  return f[0,60]+'..'+f[-60,60]+d if f.size>128
  return f+d
end
read_cache() click to toggle source
# File lib/exedb.rb, line 257
  def read_cache
    File.open(@path, File::RDONLY) { |file|
      file.flock(File::LOCK_EX)
      @content=file.read
#      warn "CACHE READ: #{@content}"
      begin
        File.open("#{@path}.code", File::RDONLY) { |code_file|
          c=code_file.gets
          c =~ /([0-9-]+)/
          @code=$1.to_i
        }
        rescue
          @code=-1
        end
      file.flock(File::LOCK_UN)
    }
  end