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!

Signal Summary

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.
Note: 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

- (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.
Note: 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

- (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
load_plugins 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 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
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

- (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)