redjazz96/nova

View on GitHub
lib/nova/project.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'yaml'

module Nova

  # A Nova project, containing the galaxy and configuration
  # settings for that project.
  class Project

    # The default paths to load from.
    DEFAULT_PATHS = [File.absolute_path("../../../galaxy", __FILE__)]

    # Whether or not the given directory is deemable as a Nova
    # project.
    #
    # @param dir [String] the directory to test.
    # @return [Boolean]
    def self.valid?(dir)
      Dir.new(dir).each.include?("nova.yml")
    end

    # The directory the project is based in.
    #
    # @return [Dir]
    attr_reader :directory

    # The load paths for this project.
    #
    # @return [Array<String>]
    attr_reader :load_paths

    # The options that were loaded from the config for this project.
    #
    # @return [Hash]
    attr_reader :options

    # Initializes the project.  Loads the configuration file by
    # default.
    #
    # @param dir [String] the path to the directory for the project.
    # @param load_config [Boolean] whether or not to load the
    #   configuration file.
    def initialize(dir, load_config = true)
      @directory = Dir.new(dir)
      @load_paths = DEFAULT_PATHS.dup
      @options   = {}

      if load_config
        load_config!
      end
    end


    # Loads the configuration file.
    #
    # @return [Hash] the data.
    def load_config!
      return unless options.empty?

      data = ::YAML.load_file(File.open("#{directory.path}/nova.yml", "r"))
      load_paths.push(*data.fetch("load_paths", []))

      load_paths.map! do |path|
        File.absolute_path(path, directory.path)
      end

      @options = Common::Metadata::Options.new(data)
    end

    # Requires all of the star files that is in the project.
    #
    # @return [void]
    def require_files
      @load_paths.each do |path|
        Dir["#{path}/**/*"].each do |f|
          require f
        end
      end
    end

    # Runs the servers defined in the options.
    #
    # @note If do_fork is false, only the first server in the config
    #   file will actually be created.
    # @param do_fork [Boolean] whether or not to actually fork the
    #   process when creating servers.
    # @param which [Array<String>] which servers to run.  Defaults to
    #   all of them.
    # @return [void]
    def run_servers(do_fork = true, which = [])
      each_server(which) do |server, name|
        puts name

        if File.exists?(server[:files][:pid])
          Nova.logger.warn {
            "PID file #{server[:files][:pid]} already exists. " +
            "Ignoring server definition."
          }
          next
        end

        if do_fork
          process_id = fork
        end

        if process_id
          File.open(server[:files][:pid], "w") { |f| f.write process_id }
          Process.detach(process_id)
        else
          return build_server(server, do_fork)
        end
      end
    end

    # Takes down running servers.
    #
    # @param which [Array<String>] which servers to take down.
    #   Defaults to all of them.
    # @return [void]
    def shoot(which = [])
      each_server do |server, name|
        if File.exists?(server[:files][:pid])
          pid = File.open(server[:files][:pid], "r") { |f| f.read }.to_i

          print "Sending INT to #{pid}... "

          Process.kill :INT, pid rescue Errno::ESRCH

          File.delete(server[:files][:pid]) rescue Errno::ENOENT

          puts "OK!"
        end
      end
    end

    private

    # Loops over all of the defined servers, yielding the server
    # definition and the index for that server.
    #
    # @yieldparam server [Hash<Symbol, Object>] the server data
    # @yieldparam server_name [String] the name of the server.
    # @yieldparam index [Numeric] the index of the server in the
    #   definition file.
    # @return [void]
    def each_server(only = [])
      server_list = [options["servers"], options["server"]].flatten.compact

      server_list.each_with_index do |srv, i|
        srv_name = srv.fetch(:name, "server#{i}")
        srv[:files] ||= {}

        files = {}
        {:log => :log, :pid => :pid, :client => :rb}.each do |f, n|
          files[f] = File.absolute_path(
            srv[:files].fetch(f, "./#{srv_name}.#{n}"),
            directory.path
          )
        end
        srv[:files] = files

        next unless only.empty? or only.include?(srv_name)

        yield srv, srv_name, i
      end
    end

    # Creates a server, with the given server options.
    #
    # @param server [Hash<Symbol, Object>] the server information to
    #   base the server instance off of.
    # @param redirect [Boolean] whether or not to redirect the
    #   {Nova.logger} and the stdin, stdout, and stderr for the
    #   server.
    # @return [void]
    def build_server(server, redirect = true)
      if redirect
        Nova.logger = Logger.new(server[:files][:log], 10, 1_024_000)
        $stdin = $stdout = $stderr = File.open("/dev/null", "a")
      end

      begin
        s = Nova::Starbound::Server.new(server)
        s.read_client_file server[:files][:client]

        trap :INT do
          s.shutdown
          File.delete(server[:files][:pid]) rescue Errno::ENOENT
        end

        s.listen
      rescue => e
        Nova.logger.fatal { "#{e}: #{e.message} => #{e.backtrace[0]}" }
        File.delete(server[:files][:pid]) rescue Errno::ENOENT
        exit
      end
    end

  end
end