Add deferred loading for plugins which's requirements aren't all met yet #256

This commit is contained in:
Felix Schäfer 2011-08-25 18:46:53 +02:00
parent 2753973ffe
commit a24a24eb19
5 changed files with 86 additions and 9 deletions

View File

@ -38,7 +38,7 @@ class AdminController < ApplicationController
end end
def plugins def plugins
@plugins = Redmine::Plugin.all @plugins = Redmine::Plugin.all.sort
end end
# Loads the default configuration # Loads the default configuration

View File

@ -63,6 +63,10 @@ Rails::Initializer.run do |config|
# It will automatically turn deliveries on # It will automatically turn deliveries on
config.action_mailer.perform_deliveries = false config.action_mailer.perform_deliveries = false
# Use redmine's custom plugin locater
require File.join(RAILS_ROOT, "lib/redmine_plugin_locator")
config.plugin_locators << RedminePluginLocator
# Load any local configuration that is kept out of source control # Load any local configuration that is kept out of source control
# (e.g. patches). # (e.g. patches).
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb')) if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))

View File

@ -13,8 +13,24 @@
module Redmine #:nodoc: module Redmine #:nodoc:
class PluginNotFound < StandardError; end class PluginError < StandardError
class PluginRequirementError < StandardError; end attr_reader :plugin_id
def initialize(plug_id=nil)
super
@plugin_id = plug_id
end
end
class PluginNotFound < PluginError
def to_s
"Missing the plugin #{@plugin_id}"
end
end
class PluginCircularDependency < PluginError
def to_s
"Circular plugin dependency in #{@plugin_id}"
end
end
class PluginRequirementError < PluginError; end
# Base class for Redmine plugins. # Base class for Redmine plugins.
# Plugins are registered using the <tt>register</tt> class method that acts as the public constructor. # Plugins are registered using the <tt>register</tt> class method that acts as the public constructor.
@ -39,9 +55,11 @@ module Redmine #:nodoc:
# #
# When rendered, the plugin settings value is available as the local variable +settings+ # When rendered, the plugin settings value is available as the local variable +settings+
class Plugin class Plugin
@registered_plugins = {} @registered_plugins = ActiveSupport::OrderedHash.new
@deferred_plugins = {}
class << self class << self
attr_reader :registered_plugins attr_reader :registered_plugins, :deferred_plugins
private :new private :new
def def_field(*names) def def_field(*names)
@ -59,6 +77,7 @@ module Redmine #:nodoc:
# Plugin constructor # Plugin constructor
def self.register(id, &block) def self.register(id, &block)
id = id.to_sym
p = new(id) p = new(id)
p.instance_eval(&block) p.instance_eval(&block)
# Set a default name if it was not provided during registration # Set a default name if it was not provided during registration
@ -67,17 +86,45 @@ module Redmine #:nodoc:
# YAML translation files should be found under <plugin>/config/locales/ # YAML translation files should be found under <plugin>/config/locales/
::I18n.load_path += Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', id.to_s, 'config', 'locales', '*.yml')) ::I18n.load_path += Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', id.to_s, 'config', 'locales', '*.yml'))
registered_plugins[id] = p registered_plugins[id] = p
# If there are plugins waiting for us to be loaded, we try loading those, again
if deferred_plugins[id]
deferred_plugins[id].each do |ary|
plugin_id, block = ary
register(plugin_id, &block)
end
deferred_plugins.delete(id)
end
return p
rescue PluginNotFound => e
# find circular dependencies
raise PluginCircularDependency.new(id) if self.dependencies_for(e.plugin_id).include?(id)
if RedminePluginLocator.instance.has_plugin? e.plugin_id
# The required plugin is going to be loaded later, defer loading this plugin
(deferred_plugins[e.plugin_id] ||= []) << [id, block]
return p
else
raise
end
end end
# Returns an array off all registered plugins # returns an array of all dependencies we know of for plugin id
# (might not be complete at all times!)
def self.dependencies_for(id)
direct_deps = deferred_plugins.keys.find_all{|k| deferred_plugins[k].collect(&:first).include?(id)}
direct_deps.inject([]) {|deps,v| deps << v; deps += self.dependencies_for(v)}
end
# Returns an array of all registered plugins
def self.all def self.all
registered_plugins.values.sort registered_plugins.values
end end
# Finds a plugin by its id # Finds a plugin by its id
# Returns a PluginNotFound exception if the plugin doesn't exist # Returns a PluginNotFound exception if the plugin doesn't exist
def self.find(id) def self.find(id)
registered_plugins[id.to_sym] || raise(PluginNotFound) registered_plugins[id.to_sym] || raise(PluginNotFound.new(id.to_sym))
end end
# Clears the registered plugins hash # Clears the registered plugins hash

View File

@ -0,0 +1,22 @@
class RedminePluginLocator < Rails::Plugin::FileSystemLocator
def initialize(initializer)
super
@@instance = self
end
def self.instance
@@instance
end
# This locator is not meant for loading plugins
# The plugin loading is done by the default rails locator, this one is
# only for querying available plugins easily
def plugins(for_loading = true)
return [] if for_loading
super()
end
def has_plugin?(name)
plugins(false).collect(&:name).include? name.to_s
end
end

View File

@ -36,7 +36,11 @@ namespace :db do
desc 'Migrate plugins to current status.' desc 'Migrate plugins to current status.'
task :plugins => :environment do task :plugins => :environment do
Engines.plugins.each do |plugin| redmine_plugins = Redmine::Plugin.all.collect(&:id)
engines_plugins = Engines.plugins.collect(&:name).collect(&:to_sym)
load_order = (engines_plugins - redmine_plugins) + (redmine_plugins & engines_plugins)
load_order.each do |p|
plugin = Engines.plugins[p]
next unless plugin.respond_to?(:migration_directory) next unless plugin.respond_to?(:migration_directory)
next unless File.exists? plugin.migration_directory next unless File.exists? plugin.migration_directory
puts "Migrating plugin #{plugin.name} ..." puts "Migrating plugin #{plugin.name} ..."