# -*- ruby -*- #encoding: utf-8 require 'arborist' unless defined?( Arborist ) require 'arborist/client' # The Arborist entity responsible for observing changes to the tree and # reporting on them. class Arborist::Observer extend Loggability, Arborist::MethodUtilities # Loggability API -- write logs to the Arborist log host log_to :arborist autoload :Action, 'arborist/observer/action' autoload :Summarize, 'arborist/observer/summarize' ## # The key for the thread local that is used to track instances as they're # loaded. LOADED_INSTANCE_KEY = :loaded_observer_instances ## # The glob pattern to use for searching for observers OBSERVER_FILE_PATTERN = '**/*.rb' Arborist.add_dsl_constructor( self ) do |description, &block| Arborist::Observer.new( description, &block ) end ### Overridden to track instances of created nodes for the DSL. def self::new( * ) new_instance = super Arborist::Observer.add_loaded_instance( new_instance ) return new_instance end ### Record a new loaded instance if the Thread-local variable is set up to track ### them. def self::add_loaded_instance( new_instance ) instances = Thread.current[ LOADED_INSTANCE_KEY ] or return instances << new_instance end ### Load the specified +file+ and return any new Nodes created as a result. def self::load( file ) self.log.info "Loading observer file %s..." % [ file ] Thread.current[ LOADED_INSTANCE_KEY ] = [] Kernel.load( file ) return Thread.current[ LOADED_INSTANCE_KEY ] ensure Thread.current[ LOADED_INSTANCE_KEY ] = nil end ### Return an iterator for all the observers supplied by the specified +loader+. def self::each_in( loader ) return loader.observers end ### Create a new Observer with the specified +description+. def initialize( description, &block ) @description = description @subscriptions = [] @actions = [] self.instance_exec( &block ) if block end ###### public ###### ## # The observer's description attr_reader :description ## # The observer's actions attr_reader :actions ## # The source file the observer was loaded from attr_accessor :source # # DSL Methods # ### Specify a pattern for events the observer is interested in. Options: ### to:: ### the name of the event; defaults to every event type ### where:: ### a Hash of criteria to match against event data ### on:: ### the identifier of the node to subscribe on, defaults to the root node ## which receives all node events. def subscribe( to: nil, where: {}, exclude: {}, on: nil ) @subscriptions << { criteria: where, exclude: exclude, identifier: on, event_type: to } end ### Register an action that will be taken when a subscribed event is received. def action( options={}, &block ) @actions << Arborist::Observer::Action.new( options, &block ) end ### Register a summary action. def summarize( options={}, &block ) @actions << Arborist::Observer::Summarize.new( options, &block ) end ### Return a client singleton for optional observer callbacks to the ### manager. def client return Arborist::Client.instance end # # Observe Methods # ### Fetch the descriptions of which events this Observer would like to receive. If no ### subscriptions have been specified, a subscription that will match any event is returned. def subscriptions # Subscribe to all events if there are no subscription criteria. self.subscribe if @subscriptions.empty? return @subscriptions end ### Handle a published event. def handle_event( uuid, event ) self.actions.each do |action| action.handle_event( event ) end end ### Return an Array of timer callbacks of the form: ### ### [ interval_seconds, callable ] ### def timers return self.actions.map do |action| next nil unless action.respond_to?( :on_timer ) && action.time_threshold.nonzero? [ action.time_threshold, action.method(:on_timer) ] end.compact end end # class Arborist::Observer