class Potluck::Dish

Constants

LAUNCHCTL_ERROR_REGEX
PLIST_DOCTYPE
PLIST_XML
SERVICE_PREFIX

Public Class Methods

new(logger: nil, is_local: nil) click to toggle source
# File lib/potluck/dish.rb, line 13
def initialize(logger: nil, is_local: nil)
  @logger = logger
  @is_local = is_local.nil? ? (IS_MACOS && ensure_launchctl! rescue false) : is_local
end

Private Class Methods

launchctl_name() click to toggle source
# File lib/potluck/dish.rb, line 116
def self.launchctl_name
  "#{SERVICE_PREFIX}#{service_name}"
end
plist(content) click to toggle source
# File lib/potluck/dish.rb, line 124
    def self.plist(content)
      <<~EOS
        #{PLIST_XML}
        #{PLIST_DOCTYPE}
        <plist version="1.0">
        <dict>
          <key>Label</key>
          <string>#{launchctl_name}</string>
          <key>RunAtLoad</key>
          <true/>
          <key>KeepAlive</key>
          <false/>
          #{content.gsub(/^/, '  ').strip}
        </dict>
        </plist>
      EOS
    end
plist_path() click to toggle source
# File lib/potluck/dish.rb, line 120
def self.plist_path
  File.join(DIR, "#{launchctl_name}.plist")
end
pretty_name() click to toggle source
# File lib/potluck/dish.rb, line 108
def self.pretty_name
  @pretty_name ||= self.to_s.split('::').last
end
service_name() click to toggle source
# File lib/potluck/dish.rb, line 112
def self.service_name
  @service_name ||= pretty_name.downcase
end

Public Instance Methods

ensure_launchctl!() click to toggle source
# File lib/potluck/dish.rb, line 18
def ensure_launchctl!
  @@launchctl = `which launchctl` && $? == 0 unless defined?(@@launchctl)
  @@launchctl || raise("Cannot manage #{self.class.to_s.split('::').last}: launchctl not found")
end
ensure_plist() click to toggle source
# File lib/potluck/dish.rb, line 23
def ensure_plist
  File.write(self.class.plist_path, self.class.plist)
end
log(message, error = false) click to toggle source
# File lib/potluck/dish.rb, line 89
def log(message, error = false)
  if @logger
    error ? @logger.error(message) : @logger.info(message)
  else
    error ? $stderr.puts(message) : $stdout.puts(message)
  end
end
restart() click to toggle source
# File lib/potluck/dish.rb, line 70
def restart
  return unless @is_local && ensure_launchctl!

  stop
  start
end
run(command, redirect_stderr: true) click to toggle source
# File lib/potluck/dish.rb, line 77
def run(command, redirect_stderr: true)
  output = `#{command}#{' 2>&1' if redirect_stderr}`
  status = $?

  if status != 0
    output.split("\n").each { |line| log(line, :error) }
    raise("Command exited with status #{status.to_i}: #{command}")
  else
    output
  end
end
start() click to toggle source
# File lib/potluck/dish.rb, line 41
def start
  return unless @is_local && ensure_launchctl!

  ensure_plist

  case status
  when :error then stop
  when :active then return
  end

  run("launchctl bootstrap gui/#{Process.uid} #{self.class.plist_path}")
  wait { status == :inactive }

  raise("Could not start #{self.class.pretty_name}") if status != :active

  log("#{self.class.pretty_name} started")
end
status() click to toggle source
# File lib/potluck/dish.rb, line 27
def status
  return :inactive unless @is_local && ensure_launchctl!

  output = `launchctl list 2>&1 | grep #{SERVICE_PREFIX}#{self.class.service_name}`

  if $? != 0
    :inactive
  elsif output[LAUNCHCTL_ERROR_REGEX]
    :error
  else
    :active
  end
end
stop() click to toggle source
# File lib/potluck/dish.rb, line 59
def stop
  return unless @is_local && ensure_launchctl! && status != :inactive

  run("launchctl bootout gui/#{Process.uid}/#{self.class.launchctl_name}")
  wait { status != :inactive }

  raise("Could not stop #{self.class.pretty_name}") if status != :inactive

  log("#{self.class.pretty_name} stopped")
end

Private Instance Methods

wait(timeout = 30, &block) click to toggle source
# File lib/potluck/dish.rb, line 99
def wait(timeout = 30, &block)
  while block.call && timeout > 0
    reduce = [[(30 - timeout.to_i) / 5.0, 0.1].max, 1].min
    timeout -= reduce

    sleep(reduce)
  end
end