Class: Ruber::ComponentManager

Inherits:
Qt::Object
  • Object
show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/ruber/component_manager.rb

Defined Under Namespace

Classes: CircularDep, DependencyError, DepsSolver, InvalidPDF, MissingPlugins, PluginSorter, UnresolvedDep

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Enumerable

#find!

Constructor Details

#initializeComponentManager

Creates a new ComponentManager



524
525
526
527
528
529
# File 'lib/ruber/component_manager.rb', line 524

def initialize
  super
  @components = Dictionary[:components, self]
  @features = {:components => self}
  @plugin_description = PluginSpecification.full({:name => :components, :class => self.class})
end

Instance Attribute Details

#plugin_descriptionObject (readonly)

Returns the PluginSpecification describing the ComponentManager



521
522
523
# File 'lib/ruber/component_manager.rb', line 521

def plugin_description
  @plugin_description
end

Class Method Details

.fill_dependencies(to_load, availlable) ⇒ Object

Finds all the dependencies for the given plugins choosing among a list.

<i>to_load</i> is an array containing the +PluginSpecification+ for the plugins to load,
while _availlable_ is an array containing the plugins which can be used to satisfy
the dependencies.

This method uses <tt>DepsSolver#solve</tt>, so see the documentation for it for
a more complete description.


486
487
488
489
# File 'lib/ruber/component_manager.rb', line 486

def self.fill_dependencies to_load, availlable
  solver = DepsSolver.new to_load, availlable
  solver.solve
end

.find_plugins(dirs, info = false) ⇒ Object

Looks in the directories specified in the dirs array for plugins and returns

a hash having the directory of each found plugin as keys and either the name
or the PluginSpecification for each plugin as values, depending on the value of the
_info_ parameter.

<b>Note:</b> if more than one directory contains a plugin with the given name,
only the first (according to the order in which directories are in _dirs_) will
be taken into account.


429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/ruber/component_manager.rb', line 429

def self.find_plugins dirs, info = false
  res = {}
  dirs.each do |dir|
    Dir.entries(dir).sort[2..-1].each do |name|
      next if res[name.to_sym]
      d = File.join dir, name
      if File.directory?(d) and File.exist?(File.join d, 'plugin.yaml')
        if info then 
          res[name.to_sym] = PluginSpecification.intro(File.join d, 'plugin.yaml')
        else res[name.to_sym] = d
        end
      end
    end
  end
  res
end

.resolve_features(pdfs, extra = []) ⇒ Object

Replaces features in plugin dependencies with the names of the plugin providing

them. _pdfs_ is an array containing the <tt>Ruber::PluginSpecification</tt>s of plugins whose dependencies should
be changed, while _extra_ is an array containing the <tt>PluginSpecification</tt>s of plugins
which should be used to look up features, but which should not be changed. For
example, _extra_ may contain descriptions for plugins which are already loaded.

It returns an array containing a copy of the <tt>Ruber::PluginSpecification</tt>s whith the dependencies
correctly changed. If a dependency is unknown, <tt>Ruber::ComponentManager::UnresolvedDep</tt>
will be raised.


458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/ruber/component_manager.rb', line 458

def self.resolve_features pdfs, extra = []
  features = (pdfs+extra).inject({}) do |res, pl|
    pl.features.each{|f| res[f] = pl.name}
    res
  end
  missing = Hash.new{|h, k| h[k] = []}
  new_pdfs = pdfs.map do |pl|
    res = pl.deep_copy
    res.deps = pl.deps.map do |d| 
      f = features[d]
      missing[pl.name] << d unless f
      f
    end.uniq.compact
    res
  end
  raise UnresolvedDep.new Hash[missing] unless missing.empty?
  new_pdfs
end

.sort_plugins(pdfs, known = []) ⇒ Object

Sorts the plugins in the pdfs array, according with their dependencies

and returns an array containing the plugin descriptions sorted in dependence order,
from the dependence to the dependent.

_known_ is an array of either symbols or <tt>Ruber::PluginSpecification</tt>s corresponding
to plugins which can be depended upon but which shouldn't be sorted with the 
others (for example, because they're already loaded).

If some of the plugins have dependency which doesn't correspond neither to another
plugin nor to one of the knonw plugins, <tt>Ruber::ComponentManager::UnresolvedDep</tt>
will be raised.

If there's a circular dependency among the plugins, <tt>Ruber::ComponentManager::CircularDep</tt>
will be raised.


507
508
509
# File 'lib/ruber/component_manager.rb', line 507

def self.sort_plugins pdfs, known = []
  PluginSorter.new( pdfs, known ).sort_plugins
end

Instance Method Details

#add(comp) ⇒ Object

For internal use only

Adds the given component to the list of components and at the end of the list
of sorted components.


603
604
605
606
# File 'lib/ruber/component_manager.rb', line 603

def add comp
  @components<< [comp.component_name, comp]
  comp.plugin_description.features.each{|f| @features[f] = comp}
end

#component_nameObject Also known as: plugin_name

Returns :components



532
533
534
# File 'lib/ruber/component_manager.rb', line 532

def component_name
  @plugin_description.name
end

#componentsObject

Returns an array containing all the loaded components, in loading order



568
569
570
# File 'lib/ruber/component_manager.rb', line 568

def components
  @components.inject([]){|res, i| res << i[1]}
end

#each_component(order = :normal) ⇒ Object

Calls the block for each component, passing it as argument to the block. The

components are passed in reverse loading order (i.e., the last loaded component
will be the first passed to the block.)


577
578
579
580
581
# File 'lib/ruber/component_manager.rb', line 577

def each_component order = :normal #:yields: comp
  if order == :reverse then @components.reverse_each{|k, v| yield v}
  else @components.each{|k, v| yield v}
  end
end

#each_plugin(order = :normal) ⇒ Object Also known as: each

Calls the block for each plugin (that is, for every component of class

<tt>Ruber::Plugin</tt> or derived), passing it as argument to the block. The
plugins are passed in reverse loading order (i.e., the last loaded plugin
will be the first passed to the block.)


589
590
591
592
593
594
# File 'lib/ruber/component_manager.rb', line 589

def each_plugin order = :normal #:yields: plug
  meth = @components.method(order == :reverse ? :reverse_each : :each)
  meth.call do |k, v| 
    yield v if v.is_a?(Ruber::Plugin)
  end
end

#load_component(name) ⇒ Object

Loads the component with name name.

_name_ is the name of a subdirectory (called the <i>component directory</i> 
in the directory where <tt>component_manager.rb</tt>
is. That directory should contain the PDF file for the component to load.

The loading process works as follows:
* the component directory is added to the KDE resource dirs for the +pixmap+,
  +data+ and +appdata+ resource types.
* A full <tt>Ruber::PluginSpecification</tt> is generated from the PDF
  (see <tt>Ruber::PluginSpecification.full</tt>). If the file can't
  be read, +SystemCallError+ is raised; if it isn't a valid PDF, 
  <tt>Ruber::PluginSpecification::PSFError</tt> is raised. In both cases, a message box
  warning the user is shown.
* the component object (that is, an instance of the class specified in the +class+
  entry of the PDF) is created
* the <tt>component_loaded(QObject*)</tt> signal is emitted, passing the component
  object as argument
* the component object is returned.

<b>Note:</b> this method doesn't insert the component object in the components
list: the component should take care to do it itself, using the +add+ method.


632
633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/ruber/component_manager.rb', line 632

def load_component name
  dir = File.expand_path File.join(File.dirname(__FILE__), name)
  if KDE::Application.instance
    KDE::Global.dirs.add_resource_dir 'pixmap', dir
    KDE::Global.dirs.add_resource_dir 'data', dir
    KDE::Global.dirs.add_resource_dir 'appdata', dir
  end
  file = File.join dir, 'plugin.yaml'
  pdf = PluginSpecification.full file
  parent = @components[:app] || self #Ruber[:app] rescue self
  comp = pdf.class_obj.new parent, pdf
  emit component_loaded(comp)
  comp
end

#load_plugin(dir) ⇒ Object

Loads the plugin in the directory dir.

The directory _dir_ should contain the PDF for the plugin, and its last part
should correspond to the plugin name.

The loading process works as follows:
* the plugin directory is added to the KDE resource dirs for the +pixmap+,
  +data+ and +appdata+ resource types.
* A full <tt>Ruber::PluginSpecification</tt> is generated from the PDF
  (see <tt>Ruber::PluginSpecification.full</tt>). If the file can't
  be read, +SystemCallError+ is raised; if it isn't a valid PDF, 
  <tt>Ruber::PluginSpecification::PSFError</tt> is raised.
* the plugin object (that is, an instance of the class specified in the +class+
  entry of the PDF) is created
* the <tt>component_loaded(QObject*)</tt> signal is emitted, passing the component
  object as argument
* for each feature provided by the plugin, the signal <tt>feature_loaded(QString, QObject*)</tt>
  is emitted, passing the name of the feature (as string) and the plugin object
  as arguments
* for each feature _f_ provided by the plugin, a signal "unloading_f(QObject*)"
  is defined
* the plugin object is returned.

<b>Note:</b> this method doesn't insert the plugin object in the components
list: the plugin should take care to do it itself, using the +add+ method.


674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
# File 'lib/ruber/component_manager.rb', line 674

def load_plugin dir
  KDE::Global.dirs.add_resource_dir 'pixmap', dir
  KDE::Global.dirs.add_resource_dir 'data', dir
  KDE::Global.dirs.add_resource_dir 'appdata', dir
  file = File.join dir, 'plugin.yaml'
  pdf = PluginSpecification.full YAML.load(File.read(file)), dir
  pdf.directory = dir
  plug = pdf.class_obj.new pdf
  emit component_loaded(plug)
  pdf.features.each do |f| 
    self.class.class_eval{signals "unloading_#{f}(QObject*)"}
    emit feature_loaded(f.to_s, plug)
  end
  plug.send :delayed_initialize
  plug
end

#load_plugins(plugins, dirs) ⇒ Object

Makes the ComponentManager load the given plugins. It is the standard method

to load plugins, because it takes into account dependency order and features.

For each plugin, a directory with the same name and containing a file
<tt>plugin.yaml</tt> is searched in the directories in the _dirs_ array.
Directories near the beginning of the array have the precedence with respect
to those near the end of the array (that is, if a plugin is found both in the
second and in the fourth directories of _dir_, the one in the second directory
is used). If the directory for some plugins can't be found, +MissingPlugins+ is
raised.

This method attempts to resolve the features for the plugins (see
<tt>Ruber::ComponentManager.resolve_features</tt>) and to sort them, using also
the already loaded plugins, if any. If it fails, it raises +UnresolvedDep+ or
+CircularDep+.

Once the plugins have been sorted, it attempts to load each one, according to
the dependency order. The order in which independent plugins are loaded is
arbitrary (but consistent: the order will be the same every time). If a plugin
fails to load, there are several behaviours:
* if no block has been given, the exception raised by the plugin is propagated
otherwise, the block is passed with the exception as argument. Depending on the
value returned by the block, the following happens:
* if the block returns <tt>:skip</tt>, all remaining plugins are skipped and
  the method returns *true*
* if the block returns <tt>:silent</tt>, an attempt to load the remaining plugins
  is made. Other loading failures will be ignored
* if the block any other true value, then the failed plugin is ignored and an
  attempt to load the remaining plugins is made.
* if the block returns *false* or *nil*, the method immediately returns *false*

<tt>load_plugins</tt> returns *true* if all the plugins were successfully loaded
(or if some failed but the block always returned a true value) and false otherwise.

===== Notes
* After a failure, dependencies aren't recomputed. This means that most likely
  all the plugins dependent on the failed one will fail, too
* This method can be conceptually divided into two phases: plugin ordering and
  plugin loading. The first part doesn't change any state. This means that, if
  it fails, the caller is free to attempt to solve the problem (for example,
  to remove the missing plugins and the ones with invalid PDFs from the list)
  and call again <tt>load_plugins</tt>. The part which actually _does_ something
  is the second. If called twice with the same arguments, it can cause trouble,
  since no attempt to skip already-loaded plugins is made. If the caller wants
  to correct errors caused in the second phase, it should put the logic to do
  so in the block.


739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'lib/ruber/component_manager.rb', line 739

def load_plugins( plugins, dirs ) #:yields: ex
  plugins = plugins.map(&:to_s)
  plugin_files = locate_plugins dirs
  plugins = create_plugins_info plugins, plugin_files, dirs
  plugins = ComponentManager.resolve_features plugins, self.plugins.map{|pl| pl.plugin_description}
  plugins = ComponentManager.sort_plugins plugins, @features.keys
  silent = false
  plugins.each do |pl| 
    begin load_plugin File.dirname(plugin_files[pl.name.to_s])
    rescue Exception => e
      @components.delete pl.name
      if silent then next
      elsif block_given? 
        res = yield pl, e
        if res == :skip then break
        elsif res == :silent then silent = true
        elsif !res then return false
        end
      else raise
      end
    end
  end
  true
end

#pluginsObject

Returns an array containing all the loaded plugins (but not the components), in loading order



558
559
560
561
562
563
# File 'lib/ruber/component_manager.rb', line 558

def plugins
  @components.inject([]) do |res, i| 
    res << i[1] if i[1].is_a? Ruber::Plugin
    res
  end
end

#query_closeObject

Calls the query_close method of all the components (in arbitrary order).

As soon as one of them returns a false value, it stops and returns *false*. If all
the calls to <tt>query_close</tt> return a true value, *true* is returned.

This method is intented to be called from <tt>MainWindow#queryClose</tt>.


829
830
831
832
833
834
835
836
# File 'lib/ruber/component_manager.rb', line 829

def query_close 
  res = each_component(:reverse) do |c| 
    unless c.equal? self
      break false unless c.query_close 
    end
  end
  res.to_bool
end

#register_with_project(prj) ⇒ Object

Method required for the Plugin interface. Does nothing



540
541
# File 'lib/ruber/component_manager.rb', line 540

def register_with_project prj
end

#remove_from_project(prj) ⇒ Object

Method required for the Plugin interface. Does nothing



546
547
# File 'lib/ruber/component_manager.rb', line 546

def remove_from_project prj
end

#restore_session(data) ⇒ Object



846
847
848
849
850
# File 'lib/ruber/component_manager.rb', line 846

def restore_session data
  each_component do |c|
    c.restore_session data unless c.same? self
  end
end

#session_dataObject



838
839
840
841
842
843
844
# File 'lib/ruber/component_manager.rb', line 838

def session_data
  res = {}
  each_component do |c|
    res.merge! c.session_data unless c.same? self
  end
  res
end

#shutdownObject

Prepares the application for being cleanly closed. To do so, it:

* asks each plugin to save its settings
* emits the signal <tt>unloading_component(QObject*)</tt> for each component,
  in reverse loading order
* calls the shutdown method for each component (in their shutdown methods,
  plugins should emit the "closing(QObject*)" signal)
* calls the delete_later method of the plugins (not of the components)
* deletes all the features provided by plugins from the list of features
* delete all the plugins from the list of loaded components.


775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# File 'lib/ruber/component_manager.rb', line 775

def shutdown
  each_component(:reverse){|c| c.save_settings unless c.equal?(self)}
  @components[:config].write
  each_component(:reverse){|c| c.shutdown unless c.equal? self}
#       @components[:config].write
#       each_component do |c|
#         unless c.equal? self
#           if c.is_a? Plugin
#             c.plugin_description.features.each{|f| emit method("unloading_#{f}").call( c)}
#           end
#           emit unloading_component(c)
#           c.shutdown
#         end
#       end
#       each_plugin {|pl| pl.delete_later}
#       @features.delete_if{|f, pl| pl.is_a? Plugin}
#       @components.delete_if{|_, pl| pl.is_a?(Plugin)}      
end

#unload_plugin(name) ⇒ Object

Unloads the plugin called name (name must be a symbol) by doing the following:

* emit the signal "unloading_*(QObject*)" for each feature provided by the plugin
* emit the signal "unloading_component(QObject*)"
* call the +shutdown+ method of the plugin
* call the <tt>delete_later</tt> method of the plugin
* remove the features provided by the plugin from the list of features
* remove the plugin from the list of components

If _name_ corresponds to a basic component and not to a plugin, +ArgumentError+
will be raised (you can't unload a basic component).


806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
# File 'lib/ruber/component_manager.rb', line 806

def unload_plugin name
  plug = @components[name]
  if plug.nil? then raise ArgumentError, "No plugin with name #{name}"
  elsif !plug.is_a?(Plugin) then raise ArgumentError, "A component can't be unloaded"
  end
#       plug.save_settings
  plug.plugin_description.features.each do |f|
    emit method("unloading_#{f}").call( plug )
  end
  emit unloading_component plug
  plug.unload
  plug.delete_later
  plug.plugin_description.features.each{|f| @features.delete f}
  @components.delete plug.plugin_name
end

#update_project(prj) ⇒ Object

Method required for the Plugin interface. Does nothing



552
553
# File 'lib/ruber/component_manager.rb', line 552

def update_project prj
end