Class: Ruber::ComponentManager
- Inherits:
-
Qt::Object
- Object
- Qt::Object
- Ruber::ComponentManager
- 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)
-
- (Object) plugin_description
readonly
Returns the PluginSpecification describing the ComponentManager.
Class Method Summary (collapse)
-
+ (Object) fill_dependencies(to_load, availlable)
Finds all the dependencies for the given plugins choosing among a list.
-
+ (Object) find_plugins(dirs, info = false)
Looks in the directories specified in the dirs array for plugins and returns.
-
+ (Object) resolve_features(pdfs, extra = [])
Replaces features in plugin dependencies with the names of the plugin providing.
-
+ (Object) sort_plugins(pdfs, known = [])
Sorts the plugins in the pdfs array, according with their dependencies .
Instance Method Summary (collapse)
-
- (Object) add(comp)
For internal use only.
-
- (Object) component_name
(also: #plugin_name)
Returns :components.
-
- (Object) components
Returns an array containing all the loaded components, in loading order.
-
- (Object) create_plugins_info(plugins, files, dirs)
private
Attempts to create Ruber::PluginSpecifications for each plugin in the plugins.
-
- (Object) each_component(order = :normal)
Calls the block for each component, passing it as argument to the block.
-
- (Object) each_plugin(order = :normal)
(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.
-
- (ComponentManager) initialize
constructor
Creates a new ComponentManager.
-
- (Object) load_component(name)
Loads the component with name name.
-
- (Object) load_plugin(dir)
Loads the plugin in the directory dir.
-
- (Object) load_plugins(plugins, dirs)
Makes the ComponentManager load the given plugins.
-
- (Object) locate_plugins(dirs)
private
Searches the directories in the dirs array for all the subdirectories containing.
-
- (Object) plugins
Returns an array containing all the loaded plugins (but not the components), in loading order.
-
- (Object) query_close
Calls the query_close method of all the components (in arbitrary order).
-
- (Object) register_with_project(prj)
Method required for the Plugin interface.
-
- (Object) remove_from_project(prj)
Method required for the Plugin interface.
- - (Object) restore_session(data)
- - (Object) session_data
-
- (Object) shutdown
Prepares the application for being cleanly closed.
-
- (Object) unload_plugin(name)
Unloads the plugin called name (name must be a symbol) by doing the following:.
-
- (Object) update_project(prj)
Method required for the Plugin interface.
Methods included from Enumerable
Signal Summary
- - loading_component(QObject* arg1)
- - component_loaded(QObject* arg1)
- - feature_loaded(QString arg1, QObject* arg2)
- - unloading_component(QObject* arg1)
Constructor Details
- (ComponentManager) initialize
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
- (Object) plugin_description (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
+ (Object) fill_dependencies(to_load, availlable)
Finds all the dependencies for the given plugins choosing among a list. to_load 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 DepsSolver#solve, 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 |
+ (Object) find_plugins(dirs, info = false)
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.
Note: 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 |
+ (Object) resolve_features(pdfs, extra = [])
Replaces features in plugin dependencies with the names of the plugin providing them. pdfs is an array containing the Ruber::PluginSpecifications of plugins whose dependencies should be changed, while extra is an array containing the PluginSpecifications 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 Ruber::PluginSpecifications whith the dependencies correctly changed. If a dependency is unknown, Ruber::ComponentManager::UnresolvedDep 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 |
+ (Object) sort_plugins(pdfs, known = [])
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 Ruber::PluginSpecifications 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, Ruber::ComponentManager::UnresolvedDep will be raised. If there’s a circular dependency among the plugins, Ruber::ComponentManager::CircularDep 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
- (Object) add(comp)
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 |
- (Object) component_name 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 |
- (Object) components
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 |
- (Object) create_plugins_info(plugins, files, dirs) (private)
Attempts to create Ruber::PluginSpecifications for each plugin in the plugins array. The path for the PDFs is taken from files, which is an hash with the plugin names as keys and the PDFs paths as values.
If some PDFs are missing, MissingPlugins is raised. If some PDFs are invalid, InvalidPDF is raised. Otherwise, an array containing the PluginSpecifications for the plugins is returned.
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 |
# File 'lib/ruber/component_manager.rb', line 884 def create_plugins_info plugins, files, dirs missing = [] errors = [] res = plugins.map do |pl| file = files[pl] if file begin PluginSpecification.new file rescue ArgumentError, PluginSpecification::PSFError errors << file end else missing << pl end end raise MissingPlugins.new missing, dirs unless missing.empty? raise InvalidPDF.new errors unless errors.empty? res end |
- (Object) each_component(order = :normal)
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 |
- (Object) each_plugin(order = :normal) Also known as: 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. 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 |
- (Object) load_component(name)
Loads the component with name name.
name is the name of a subdirectory (called the component directory in the directory where component_manager.rb 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 Ruber::PluginSpecification is generated from the PDF (see Ruber::PluginSpecification.full). If the file can’t be read, SystemCallError is raised; if it isn’t a valid PDF, Ruber::PluginSpecification::PSFError 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 component_loaded(QObject*) signal is emitted, passing the component object as argument
- the component object is returned.
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 |
- (Object) load_plugin(dir)
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 Ruber::PluginSpecification is generated from the PDF (see Ruber::PluginSpecification.full). If the file can’t be read, SystemCallError is raised; if it isn’t a valid PDF, Ruber::PluginSpecification::PSFError is raised.
- the plugin object (that is, an instance of the class specified in the class entry of the PDF) is created
- the component_loaded(QObject*) signal is emitted, passing the component object as argument
- for each feature provided by the plugin, the signal feature_loaded(QString, QObject*) 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.
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 |
- (Object) load_plugins(plugins, dirs)
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 plugin.yaml 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 Ruber::ComponentManager.resolve_features) 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 :skip, all remaining plugins are skipped and the method returns true
- if the block returns :silent, 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
- 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 load_plugins. 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 |
- (Object) locate_plugins(dirs) (private)
Searches the directories in the dirs array for all the subdirectories containing a plugin.yaml file and returns the paths of the files. Returns a hash with keys corresponding to plugin names and values corresponding to the path of the PDF for the plugin.
861 862 863 864 865 866 867 868 869 870 871 872 |
# File 'lib/ruber/component_manager.rb', line 861 def locate_plugins dirs plugin_files = {} dirs.reverse.each do |d| Dir.entries(d).sort[2..-1].each do |f| full_dir = File.join d, f if File.directory?(full_dir) and File.exist?(File.join(full_dir, 'plugin.yaml')) plugin_files[f] = File.join full_dir, 'plugin.yaml' end end end plugin_files end |
- (Object) plugins
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 |
- (Object) query_close
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 query_close return a true value, true is returned.
This method is intented to be called from MainWindow#queryClose.
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 |
- (Object) register_with_project(prj)
Method required for the Plugin interface. Does nothing
540 541 |
# File 'lib/ruber/component_manager.rb', line 540 def register_with_project prj end |
- (Object) remove_from_project(prj)
Method required for the Plugin interface. Does nothing
546 547 |
# File 'lib/ruber/component_manager.rb', line 546 def remove_from_project prj end |
- (Object) restore_session(data)
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 |
- (Object) session_data
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 |
- (Object) shutdown
Prepares the application for being cleanly closed. To do so, it:
- asks each plugin to save its settings
- emits the signal unloading_component(QObject*) 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 |
- (Object) unload_plugin(name)
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 delete_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
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 |
- (Object) update_project(prj)
Method required for the Plugin interface. Does nothing
552 553 |
# File 'lib/ruber/component_manager.rb', line 552 def update_project prj end |
Signal Details
- loading_component(QObject* arg1)
- component_loaded(QObject* arg1)
- feature_loaded(QString arg1, QObject* arg2)
- unloading_component(QObject* arg1)