Class: Ruber::ComponentManager
Defined Under Namespace
Classes: CircularDep, DependencyError, DepsSolver, InvalidPDF, MissingPlugins, PluginSorter, UnresolvedDep
Instance Attribute Summary collapse
-
#plugin_description ⇒ Object
readonly
Returns the
PluginSpecification
describing theComponentManager
.
Class Method Summary collapse
-
.fill_dependencies(to_load, availlable) ⇒ Object
Finds all the dependencies for the given plugins choosing among a list.
-
.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.
-
.resolve_features(pdfs, extra = []) ⇒ Object
Replaces features in plugin dependencies with the names of the plugin providing them.
-
.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.
Instance Method Summary collapse
-
#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.
-
#component_name ⇒ Object
(also: #plugin_name)
Returns
:components
. -
#components ⇒ Object
Returns an array containing all the loaded components, in loading order.
-
#each_component(order = :normal) ⇒ Object
Calls the block for each component, passing it as argument to the block.
-
#each_plugin(order = :normal) ⇒ Object
(also: #each)
Calls the block for each plugin (that is, for every component of class
Ruber::Plugin
or derived), passing it as argument to the block. -
#initialize ⇒ ComponentManager
constructor
Creates a new
ComponentManager
. -
#load_component(name) ⇒ Object
Loads the component with name name.
-
#load_plugin(dir) ⇒ Object
Loads the plugin in the directory dir.
-
#load_plugins(plugins, dirs) ⇒ Object
Makes the
ComponentManager
load the given plugins. -
#plugins ⇒ Object
Returns an array containing all the loaded plugins (but not the components), in loading order.
-
#query_close ⇒ Object
Calls the
query_close
method of all the components (in arbitrary order). -
#register_with_project(prj) ⇒ Object
Method required for the Plugin interface.
-
#remove_from_project(prj) ⇒ Object
Method required for the Plugin interface.
- #restore_session(data) ⇒ Object
- #session_data ⇒ Object
-
#shutdown ⇒ Object
Prepares the application for being cleanly closed.
-
#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 thedelete_later
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). -
#update_project(prj) ⇒ Object
Method required for the Plugin interface.
Methods included from Enumerable
Constructor Details
#initialize ⇒ ComponentManager
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_description ⇒ Object (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_name ⇒ Object 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 |
#components ⇒ Object
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. 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 |
#plugins ⇒ Object
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_close ⇒ Object
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_data ⇒ Object
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 |
#shutdown ⇒ Object
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 |