# -*- coding: binary -*-

module Msf

###
#
# This class manages the loading and unloading plugins.  All plugins must
# implement the Plugin base class interface.
#
###
class PluginManager < Array

  include Framework::Offspring

  #
  # The hash of path names to classes that is used during load.
  #
  @@path_hash = {}

  #
  # Check the hash using the supplied path name to see if there is already a
  # class association.
  #
  def self.check_path_hash(path)
    @@path_hash[path]
  end

  #
  # Set the class that's associated with the supplied hash.
  #
  def self.set_path_hash(path, klass)
    @@path_hash[path] = klass
  end

  #
  # Initializes the plugin manager.
  #
  def initialize(framework)
    self.framework = framework
  end

  #
  # Loads a plugin from the supplied path and returns the instance that is
  # created as a result.
  #
  def load(path, opts = {})
    # Check to see if a plugin from this path has already been loaded
    # before.
    if ((klass = self.class.check_path_hash(path)) == nil)
      old = Msf::Plugin.constants
      require(path)
      new = Msf::Plugin.constants

      # No new classes added?
      if ((diff = new - old).empty?)
        raise RuntimeError, "No classes were loaded from #{path} in the Msf::Plugin namespace."
      end

      # Grab the class
      klass = Msf::Plugin.const_get(diff[0])

      # Cache the path to class association for future reference
      self.class.set_path_hash(path, klass)
    # If it's already been loaded, go ahead and try to re-load it in case
    # the contents have changed.
    else
      Kernel.load(path + ".rb")
    end

    # Force unloading if already loaded
    plugin = self.find { |p| p.class == klass }
    unload(plugin) if plugin

    # Create an instance of the plugin and let it initialize
    instance = klass.create(framework, opts)

    # Add it to the list of plugins
    if (self.member?(instance) == false)
      self.unshift(instance)
    end

    # Return the instance to the caller
    instance
  end

  #
  # Unloads a plugin using the instance that was returned from a previous
  # call to load.
  #
  def unload(inst)
    # If the reference count drops to zero, remove it from the list of
    # loaded plugins.  This will indirectly call the cleanup method on the
    # plugin.
    if (inst.deref == true)
      delete(inst)
    end
  end

end

end
