ShogunPanda/devdnsd

View on GitHub
lib/devdnsd/application.rb

Summary

Maintainability
A
0 mins
Test Coverage
# encoding: utf-8
#
# This file is part of the devdnsd gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
# Licensed under the MIT license, which can be found at https://choosealicense.com/licenses/mit.
#

# A small DNS server to enable local .dev domain resolution.
module DevDNSd
  # The main DevDNSd application.
  #
  # @attribute [r] config
  #   @return [Configuration] The {Configuration Configuration} of this application.
  # @attribute [r] server
  #   @return [RubyDNS::RuleBasedServer] The server of this application.
  # @attribute [r] command
  #   @return [Bovem::Command] The Bovem command.
  # @attribute logger
  #   @return [Bovem::Logger] The logger for this application.
  # @attribute [r] locale
  #   @return [Symbol|nil] The current application locale.
  # @attribute [r] :i18n
  #   @return [Bovem::I18n] A localizer object.
  class Application < Process::Daemon
    # Class for ANY DNS request.
    ANY_REQUEST = Resolv::DNS::Resource::IN::ANY

    # List of classes handled in case of DNS request with resource class ANY.
    ANY_CLASSES = [
      Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME,
      Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS,
      Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT
    ].freeze

    include DevDNSd::Aliases
    include DevDNSd::Server
    include DevDNSd::System
    include DevDNSd::OSX

    attr_reader :config
    attr_reader :command
    attr_accessor :logger
    attr_reader :locale
    attr_reader :i18n
    attr_reader :server

    # Creates a new application.
    #
    # @param command [Bovem::Command] The current Bovem command.
    # @param locale [Symbol] The locale to use for the application.
    def initialize(command, locale)
      @i18n = Bovem::I18n.new(locale, root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")
      @locale = locale
      @command = command
      options = load_options

      # Setup logger
      create_logger(options)

      # Open configuration
      read_configuration(options)

      super(working_directory)
      self
    end

    # Stops the server.
    def shutdown
      server.actors.first.links.each(&:terminate) if server
    end

    # Gets the current logger of the application.
    #
    # @param force [Boolean] If to force recreation of the logger.
    # @return [Logger] The current logger of the application.
    def logger(force = false)
      @logger = nil if force
      @logger ||= Bovem::Logger.create(@config.foreground ? $stdout : @config.log_file, level: @config.log_level, formatter: @log_formatter)
    end

    # This method is called when the server starts. By default is a no-op.
    #
    # @return [NilClass] `nil`.
    def on_start
    end

    # This method is called when the server stop.
    #
    # @return [NilClass] `nil`.
    def on_stop
    end

    # Returns a unique (singleton) instance of the application.
    #
    # @param command [Bovem::Command] The current Bovem command.
    # @param locale [Symbol] The locale to use for the application.
    # @param force [Boolean] If to force recreation of the instance.
    # @return [Application] The unique (singleton) instance of the application.
    def self.instance(command = nil, locale = nil, force = false)
      @instance = nil if force
      @instance ||= DevDNSd::Application.new(command, locale) if command
      @instance
    end

    # Runs the application in foreground.
    #
    # @see #perform_server
    def self.run
      instance.perform_server
    end

    # Stops the application.
    def self.quit
      instance.shutdown
    rescue
      Kernel.exit(1)
    end

    # Check if the current implementation supports DevDNSd.
    def self.check_ruby_implementation
      if defined?(JRuby)
        Kernel.puts(Bovem::I18n.new(root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/").no_jruby)
        Kernel.exit(0)
      end
    end

    private

    # :nodoc:
    def load_options
      @command.application.get_options.reject { |_, v| v.nil? }.merge(@command.get_options.reject { |_, v| v.nil? })
    end

    # :nodoc:
    def create_logger(options)
      warn_failure = false
      orig_file = file = Bovem::Logger.get_real_file(options["log_file"]) || Bovem::Logger.default_file

      if file.is_a?(String)
        file, warn_failure = load_logger(File.absolute_path(File.expand_path(file)), options)
      end

      finalize_create_logger(file, orig_file, warn_failure)
    end

    # :nodoc:
    def load_logger(file, options)
      warn_failure = false

      begin
        FileUtils.mkdir_p(File.dirname(file))
        @logger = Bovem::Logger.create(file, level: ::Logger::INFO)
      rescue
        options["log_file"] = "STDOUT"
        file = $stdout
        warn_failure = true
      end

      [file, warn_failure]
    end

    # :nodoc:
    def finalize_create_logger(file, orig_file, warn_failure)
      @logger = Bovem::Logger.create(file, level: ::Logger::INFO)
      @logger.warn(replace_markers(i18n.logging_failed(orig_file))) if @logger && warn_failure
      @logger
    end

    # :nodoc:
    def read_configuration(options)
      path = ::File.absolute_path(File.expand_path(options["configuration"]))

      begin
        @config = DevDNSd::Configuration.new(path, options, @logger)
        ensure_directory_for(@config.log_file) if @config.log_file.is_a?(String)
        ensure_directory_for(@config.pid_file)
        @logger = logger(true)
      rescue Bovem::Errors::InvalidConfiguration => e
        log_failed_configuration(path, e)
        shutdown
      end
    end

    # :nodoc:
    def ensure_directory_for(path)
      FileUtils.mkdir_p(File.dirname(path))
    rescue
      @logger.warn(replace_markers(i18n.invalid_directory(File.dirname(path))))
      shutdown
      Kernel.exit(1)
    end

    # :nodoc:
    def log_failed_configuration(path, exception)
      logger = Bovem::Logger.create($stderr)
      logger.fatal(exception.message)
      logger.warn(replace_markers(i18n.application_create_config(path)))
    end
  end
end