Add deferred loading for plugins which's requirements aren't all met yet #256
This commit is contained in:
parent
2753973ffe
commit
a24a24eb19
|
@ -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
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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} ..."
|
||||||
|
|
Loading…
Reference in New Issue