adhearsion/punchblock

View on GitHub
lib/punchblock/translator/dtmf_recognizer.rb

Summary

Maintainability
A
1 hr
Test Coverage
# encoding: utf-8

module Punchblock
  module Translator
    class DTMFRecognizer
      class BuiltinMatcherCache
        include Singleton
        include MonitorMixin

        def get(uri)
          cache[uri] ||= fetch(uri)
        end

        private

        def fetch(uri)
          grammar = RubySpeech::GRXML.from_uri(uri)
          RubySpeech::GRXML::Matcher.new(grammar)
        end

        def cache
          @cache ||= {}
        end
      end

      include Celluloid

      def initialize(responder, grammar, initial_timeout = nil, inter_digit_timeout = nil, terminator = nil)
        @responder = responder
        self.initial_timeout = initial_timeout || -1
        self.inter_digit_timeout = inter_digit_timeout || -1
        @terminator = terminator
        @finished = false

        @matcher = if grammar.url
          BuiltinMatcherCache.instance.get(grammar.url)
        else
          RubySpeech::GRXML::Matcher.new RubySpeech::GRXML.import(grammar.value.to_s)
        end
        @buffer = ""
      end

      def <<(digit)
        cancel_initial_timer
        @buffer << digit unless terminating?(digit)
        case (match = get_match)
        when RubySpeech::GRXML::NoMatch
          finalize :nomatch
        when RubySpeech::GRXML::MaxMatch
          finalize :match, match
        when RubySpeech::GRXML::Match
          finalize :match, match if terminating?(digit)
        when RubySpeech::GRXML::PotentialMatch
          finalize :nomatch if terminating?(digit)
        end
        reset_inter_digit_timer unless @finished
      end

      def start_timers
        begin_initial_timer @initial_timeout/1000 unless @initial_timeout == -1
      end

      private

      def terminating?(digit)
        digit == @terminator
      end

      def get_match
        @matcher.match @buffer.dup
      end

      def initial_timeout=(other)
        raise OptionError, 'An initial timeout value that is negative (and not -1) is invalid.' if other < -1
        @initial_timeout = other
      end

      def inter_digit_timeout=(other)
        raise OptionError, 'An inter-digit timeout value that is negative (and not -1) is invalid.' if other < -1
        @inter_digit_timeout = other
      end

      def begin_initial_timer(timeout)
        @initial_timer = after timeout do
          finalize :noinput
        end
      end

      def cancel_initial_timer
        return unless instance_variable_defined?(:@initial_timer) && @initial_timer
        @initial_timer.cancel
        @initial_timer = nil
      end

      def reset_inter_digit_timer
        return if @inter_digit_timeout == -1
        @inter_digit_timer ||= begin
          after @inter_digit_timeout/1000 do
            case (match = get_match)
            when RubySpeech::GRXML::Match
              finalize :match, match
            else
              finalize :nomatch
            end
          end
        end
        @inter_digit_timer.reset
      end

      def cancel_inter_digit_timer
        return unless instance_variable_defined?(:@inter_digit_timer) && @inter_digit_timer
        @inter_digit_timer.cancel
        @inter_digit_timer = nil
      end

      def finalize(match_type, match = nil)
        cancel_initial_timer
        cancel_inter_digit_timer
        if match
          @responder.send match_type, match
        else
          @responder.send match_type
        end
        @finished = true
        terminate
      end
    end
  end
end