class MiGA::Lair

Lair of MiGA Daemons handling job submissions
#

def check_directories

Traverse directories checking MiGA projects
#
def check_directories
  each_project do |project|
    d = project_daemon(project)
    next if d.active?
    l_alive = d.last_alive
    unless l_alive.nil?
      next if options[:trust_timestamp] && project.metadata.updated < l_alive
      next if l_alive > Time.now - options[:wait_for]
    end
    launch_daemon(project)
  end
end

def daemon_first_loop

First loop of the lair's chief daemon
#
def daemon_first_loop
  say '-----------------------------------'
  say '%s launched' % daemon_name
  say '-----------------------------------'
  say 'Configuration options:'
  say options.to_s
end

def daemon_loop

Run one loop step. Returns a Boolean indicating if the loop should continue.
#
def daemon_loop
  check_directories
  return false if options[:dry]
  sleep(options[:latency])
  true
end

def daemon_name

Name of the lair's chief daemon
#
def daemon_name
  "MiGA:#{options[:name]}"
end

def each_daemon(include_self = true)

if +include_self+.
Perform block for each daemon, including the chief daemon
#
def each_daemon(include_self = true)
  yield(self) if include_self
  each_project { |project| yield(project_daemon(project)) }
end

def each_project(dir = path)

subdirectories that are not MiGA projects.
Searches for MiGA projects recursively in all
passing the absolute path of the project to the block.
Perform block for each project in the +dir+ directory,
#
def each_project(dir = path)
  Dir.entries(dir).each do |f|
    next if %w[. ..].include?(f) # Ruby <= 2.3 doesn't have Dir.children
    f = File.join(dir, f)
    if MiGA::Project.exist? f
      project = MiGA::Project.load(f)
      raise "Cannot load project: #{f}" if project.nil?
      yield(project) unless options[:exclude].include?(project.name)
    elsif Dir.exist? f
      each_project(f) { |p| yield(p) }
    end
  end
end

def initialize(path, opts = {})

- exclude: Array of project names to be excluded from the lair
them
- dry: Only report when daemons would be launched, but don't actually launch
by default: true
project is to be trusted to determine changes in the project,
- trust_timestamp: boolean indicating if the +modified+ timestamp of the
- name: A name for the chief daemon process, by default: basename of +path+
inactive (when all tasks are complete), by default: false
- keep_inactive: boolean indicating if daemons should stay alive even when
by default: 30
- wait_for: time to wait for a daemon to report being alive in seconds,
- latency: time to wait between iterations in seconds, by default: 120
- json: json definition for all children daemons, by default: nil
to wake the chief daemon. Supported options include:
Initialize an inactive daemon for the directory at +path+. See #daemon
#
def initialize(path, opts = {})
  @path = File.expand_path(path)
  @options = opts
  {
    json: nil,
    latency: 30,
    wait_for: 30,
    keep_inactive: false,
    trust_timestamp: true,
    name: File.basename(@path),
    dry: false,
    exclude: []
  }.each { |k, v| @options[k] = v if @options[k].nil? }
end

def launch_daemon(project)

MiGA::Daemon object
Launch daemon for the MiGA::Project +project+ and returns the corresponding
#
def launch_daemon(project)
  say "Launching daemon: #{project.path}"
  daemon = project_daemon(project)
  daemon.runopts(:shutdown_when_done, true) unless options[:keep_inactive]
  unless options[:dry]
    daemon.start
    sleep(1) # <- to make sure the daemon started up (it takes about 0.1 secs)
  end
  daemon
end

def project_daemon(project)

Return the daemon of +project+, a MiGA::Project object
#
def project_daemon(project)
  MiGA::Daemon.new(project, options[:json])
end

def terminate_daemon(daemon)

+MiGA::Common::WithDaemon+
Send termination message to +daemon+, an object implementing
#
def terminate_daemon(daemon)
  say "Probing #{daemon.class} #{daemon.daemon_home}"
  if daemon.active?
    say 'Sending termination message'
    FileUtils.touch(daemon.terminate_file)
  end
end

def terminate_daemons

Terminate all daemons in the lair (including the chief daemon)
#
def terminate_daemons
  terminate_daemon(self)
  each_project do |project|
    terminate_daemon(MiGA::Daemon.new(project))
  end
end