rsence 2.0.0.8.pre → 2.0.0.9.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +11 -3
- data/VERSION +1 -1
- data/lib/plugins/dependencies.rb +258 -11
- data/lib/plugins/dependencies.rbc +2103 -0
- data/lib/plugins/plugin.rb +2 -2
- data/lib/plugins/plugin_sqlite_db.rb +2 -5
- data/lib/plugins/pluginmanager.rb +447 -315
- data/lib/plugins/plugins.rb +2 -2
- data/lib/transporter/transporter.rb +12 -1
- data/plugins/client_pkg/info.yaml +4 -0
- data/plugins/index_html/info.yaml +4 -0
- data/plugins/main/info.yaml +4 -0
- data/plugins/ticket/info.yaml +4 -0
- metadata +17 -10
- data/conf/config.ru +0 -5
- data/conf/unicorn.conf +0 -78
data/lib/plugins/plugin.rb
CHANGED
@@ -205,7 +205,7 @@ module ::RSence
|
|
205
205
|
## # Defaults to true
|
206
206
|
## :uses_msg: true
|
207
207
|
##
|
208
|
-
## # Restore the default, when the session is restored; defaults to
|
208
|
+
## # Restore the default, when the session is restored; defaults to true
|
209
209
|
## :restore_default: false
|
210
210
|
##
|
211
211
|
## # List of value responder methods to bind.
|
@@ -475,7 +475,7 @@ module ::RSence
|
|
475
475
|
# :uses_msg => true # defaults to true; when false, doesn't pass the msg as the first parameter
|
476
476
|
# },
|
477
477
|
#
|
478
|
-
# # Restore the default, when the session is restored; defaults to
|
478
|
+
# # Restore the default, when the session is restored; defaults to true
|
479
479
|
# :restore_default => false,
|
480
480
|
#
|
481
481
|
# # List of value responder methods to bind.
|
@@ -14,14 +14,11 @@ module ::RSence
|
|
14
14
|
# to the sqlite database automatically created
|
15
15
|
module PluginSqliteDB
|
16
16
|
|
17
|
-
# First calls superclass, then creates database
|
17
|
+
# First calls superclass, then creates database if it doesn't exist.
|
18
18
|
# Then calls init_db_tables.
|
19
19
|
def init
|
20
20
|
super
|
21
|
-
db_dir = File.join(
|
22
|
-
unless File.directory?( db_dir )
|
23
|
-
Dir.mkdir( db_dir )
|
24
|
-
end
|
21
|
+
db_dir = File.join( RSence.args[:env_path], 'db' )
|
25
22
|
@db_path = File.join( db_dir, "#{@name}.db" )
|
26
23
|
unless File.exist?( @db_path )
|
27
24
|
@db = Sequel.sqlite( @db_path )
|
@@ -6,6 +6,7 @@
|
|
6
6
|
# with this software package. If not, contact licensing@riassence.com
|
7
7
|
##
|
8
8
|
require 'plugins/plugins'
|
9
|
+
require 'plugins/dependencies'
|
9
10
|
|
10
11
|
module RSence
|
11
12
|
|
@@ -21,32 +22,11 @@ module RSence
|
|
21
22
|
|
22
23
|
attr_reader :transporter, :sessions
|
23
24
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
if transporter
|
28
|
-
@transporter = transporter
|
29
|
-
@sessions = transporter.sessions
|
30
|
-
end
|
31
|
-
@name_prefix = name_prefix
|
32
|
-
@plugin_paths = plugin_paths
|
33
|
-
puts "Loading #{name_prefix+' ' if name_prefix}plugins..." if RSence.args[:verbose]
|
34
|
-
scan_plugins
|
35
|
-
puts "Plugins #{name_prefix+' ' if name_prefix}loaded." if RSence.args[:verbose]
|
36
|
-
if autoreload
|
37
|
-
@thr = Thread.new do
|
38
|
-
Thread.pass
|
39
|
-
while true
|
40
|
-
begin
|
41
|
-
changed_plugins!
|
42
|
-
rescue => e
|
43
|
-
warn e.inspect
|
44
|
-
end
|
45
|
-
sleep 3
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
25
|
+
# Returns the registry data for plugin bundle +plugin_name+
|
26
|
+
def registry( plugin_name )
|
27
|
+
return @registry[ plugin_name ]
|
49
28
|
end
|
29
|
+
alias [] registry
|
50
30
|
|
51
31
|
# By default, calling a method not defined calls a plugin of that name
|
52
32
|
def method_missing( sym, *args, &block )
|
@@ -59,245 +39,12 @@ module RSence
|
|
59
39
|
end
|
60
40
|
end
|
61
41
|
|
62
|
-
#
|
63
|
-
def
|
64
|
-
@
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
bundle_path = File.expand_path( File.join( path, bundle_name ) )
|
69
|
-
next unless File.directory?( bundle_path )
|
70
|
-
bundle_file = bundle_name+'.rb'
|
71
|
-
next unless File.exists?( File.join( bundle_path, bundle_file ) )
|
72
|
-
if File.exists?( File.join( bundle_path, 'disabled' ) )
|
73
|
-
if @registry.has_key?( bundle_name.to_sym )
|
74
|
-
puts "Disabling bundle #{bundle_name}..."
|
75
|
-
online_status = @transporter.online?
|
76
|
-
@transporter.online = false
|
77
|
-
unload_bundle( bundle_name.to_sym )
|
78
|
-
@transporter.online = online_status
|
79
|
-
if RSence.args[:say]
|
80
|
-
Thread.new do
|
81
|
-
Thread.pass
|
82
|
-
system(%{say "Unloaded #{bundle_name.to_s}."})
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
else
|
87
|
-
if not @registry.has_key?( bundle_name.to_sym )
|
88
|
-
puts "Loading bundle #{bundle_name}..."
|
89
|
-
online_status = @transporter.online?
|
90
|
-
@transporter.online = false
|
91
|
-
load_bundle( bundle_path, bundle_name.to_sym, bundle_name+'.rb' )
|
92
|
-
call( bundle_name.to_sym, :open )
|
93
|
-
@transporter.online = online_status
|
94
|
-
if RSence.args[:say]
|
95
|
-
Thread.new do
|
96
|
-
Thread.pass
|
97
|
-
system(%{say "Loaded #{bundle_name.to_s}."})
|
98
|
-
end
|
99
|
-
end
|
100
|
-
else
|
101
|
-
# puts "Checking if bundle #{bundle_name} is changed..."
|
102
|
-
info = @info[bundle_name.to_sym]
|
103
|
-
if info[:reloadable] and plugin_changed?( bundle_name.to_sym )
|
104
|
-
puts "Bundle #{bundle_name} has changed, reloading..."
|
105
|
-
online_status = @transporter.online?
|
106
|
-
@transporter.online = false
|
107
|
-
unload_bundle( bundle_name.to_sym )
|
108
|
-
load_bundle( bundle_path, bundle_name.to_sym, bundle_name+'.rb' )
|
109
|
-
call( bundle_name.to_sym, :open )
|
110
|
-
@transporter.online = online_status
|
111
|
-
if RSence.args[:say]
|
112
|
-
Thread.new do
|
113
|
-
Thread.pass
|
114
|
-
system(%{say "Reloaded #{bundle_name.to_s}."})
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# Unloads the plugin bundle named +bundle_name+
|
125
|
-
def unload_bundle( bundle_name )
|
126
|
-
puts "unloading bundle: #{bundle_name.inspect}" if RSence.args[:debug]
|
127
|
-
if @registry.has_key?( bundle_name )
|
128
|
-
call( bundle_name, :flush )
|
129
|
-
call( bundle_name, :close )
|
130
|
-
@registry.delete( bundle_name )
|
131
|
-
@aliases.each do |a_name,b_name|
|
132
|
-
if b_name == bundle_name
|
133
|
-
@aliases.delete( a_name )
|
134
|
-
end
|
135
|
-
end
|
136
|
-
if @servlets.include?( bundle_name )
|
137
|
-
@servlets.delete( bundle_name )
|
138
|
-
end
|
139
|
-
if @info.include?( bundle_name )
|
140
|
-
@info.delete( bundle_name )
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Returns true, if a plugin bundle has changed.
|
146
|
-
# Only compares timestamp, not checksum.
|
147
|
-
def plugin_changed?( plugin_name )
|
148
|
-
info = @info[plugin_name]
|
149
|
-
last_changed = info[:last_changed]
|
150
|
-
newest_change = most_recent( info[:path], last_changed )
|
151
|
-
return last_changed < newest_change
|
152
|
-
end
|
153
|
-
|
154
|
-
# Top-level method for scanning all plugin directories.
|
155
|
-
# Clears previously loaded plugins.
|
156
|
-
def scan_plugins
|
157
|
-
@registry = {}
|
158
|
-
@info = {}
|
159
|
-
@aliases = {}
|
160
|
-
@servlets = []
|
161
|
-
@plugin_paths.each do |path|
|
162
|
-
next unless File.directory? path
|
163
|
-
scan_plugindir( path )
|
164
|
-
end
|
165
|
-
delegate( :open )
|
166
|
-
end
|
167
|
-
|
168
|
-
# Returns the registry data for plugin bundle +plugin_name+
|
169
|
-
def registry( plugin_name )
|
170
|
-
return @registry[ plugin_name ]
|
171
|
-
end
|
172
|
-
alias [] registry
|
173
|
-
|
174
|
-
# Scans a directory of plugins, calls +load_plugin+ for bundles that match
|
175
|
-
# the definition of a plugin bundle.
|
176
|
-
# - Skips bundles starting with a dot
|
177
|
-
# - Skips bundles without a ruby source file with the same
|
178
|
-
# name as the directory (plus '.rb').
|
179
|
-
# - Skips bundles containing a file or directory named 'disabled'
|
180
|
-
def scan_plugindir( path )
|
181
|
-
Dir.entries(path).each do |bundle_name|
|
182
|
-
next if bundle_name[0].chr == '.'
|
183
|
-
bundle_path = File.expand_path( File.join( path, bundle_name ) )
|
184
|
-
next unless File.directory?( bundle_path )
|
185
|
-
bundle_file = bundle_name+'.rb'
|
186
|
-
if not File.exists?( File.join( bundle_path, bundle_file ) )
|
187
|
-
bundle_file = 'main.rb'
|
188
|
-
next unless File.exists?( File.join( bundle_path, bundle_file ) )
|
189
|
-
end
|
190
|
-
next if File.exists?( File.join( bundle_path, 'disabled' ) )
|
191
|
-
|
192
|
-
load_bundle( bundle_path, bundle_name.to_sym, bundle_file )
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
# Finds the most recent file in the path
|
197
|
-
def most_recent( bundle_path, newest_date=0 )
|
198
|
-
path_date = File.stat( bundle_path ).mtime.to_i
|
199
|
-
is_dir = File.directory?( bundle_path )
|
200
|
-
if path_date > newest_date and not is_dir
|
201
|
-
# puts "File is newer: #{bundle_path}"
|
202
|
-
newest_date = path_date
|
203
|
-
end
|
204
|
-
if is_dir
|
205
|
-
Dir.entries( bundle_path ).each do |entry_name|
|
206
|
-
next if entry_name[0].chr == '.'
|
207
|
-
full_path = File.join( bundle_path, entry_name )
|
208
|
-
unless File.directory?( full_path )
|
209
|
-
next unless entry_name.include?('.') and ['yaml','rb'].include?( entry_name.split('.')[-1] )
|
210
|
-
end
|
211
|
-
newest_date = most_recent( full_path, newest_date )
|
212
|
-
end
|
213
|
-
end
|
214
|
-
return newest_date
|
215
|
-
end
|
216
|
-
|
217
|
-
# Gets plugin information
|
218
|
-
def bundle_info( bundle_path )
|
219
|
-
|
220
|
-
bundle_name = File.split( bundle_path )[1]
|
221
|
-
|
222
|
-
# Default bundle information
|
223
|
-
info = {
|
224
|
-
# The human-readable product name of the package
|
225
|
-
:title => bundle_name.capitalize,
|
226
|
-
|
227
|
-
# The human-readable version of the package
|
228
|
-
:version => '0.0.0',
|
229
|
-
|
230
|
-
# A brief description of the package (rdoc formatting supported)
|
231
|
-
:description => 'No Description',
|
232
|
-
|
233
|
-
# A flag (when false) prevents the plugin from automatically reload when changed.
|
234
|
-
:reloadable => true,
|
235
|
-
|
236
|
-
# System version requirement.
|
237
|
-
:sys_version => '>= 1.0.0',
|
238
|
-
|
239
|
-
# Path to bundle
|
240
|
-
:path => bundle_path,
|
241
|
-
|
242
|
-
# Name of bundle
|
243
|
-
:name => bundle_name.to_sym,
|
244
|
-
|
245
|
-
# Last change
|
246
|
-
:last_changed => most_recent( bundle_path )
|
247
|
-
|
248
|
-
}
|
249
|
-
|
250
|
-
info_path = File.join( bundle_path, 'info.yaml' )
|
251
|
-
if File.exists?( info_path )
|
252
|
-
info_yaml = YAML.load( File.read( info_path ) )
|
253
|
-
info_yaml.each do |info_key,info_value|
|
254
|
-
info[ info_key.to_sym ] = info_value
|
255
|
-
end
|
256
|
-
end
|
257
|
-
return info
|
258
|
-
|
259
|
-
end
|
260
|
-
|
261
|
-
# Loads a plugin bundle.
|
262
|
-
def load_bundle( bundle_path, bundle_name, bundle_file )
|
263
|
-
puts "loading bundle: #{bundle_name.inspect}" if RSence.args[:debug]
|
264
|
-
if @registry.has_key?( bundle_name.to_sym )
|
265
|
-
warn "Warning: Bundle #{bundle_name} already loaded."
|
266
|
-
return
|
267
|
-
end
|
268
|
-
|
269
|
-
bundle_file_path = File.join( bundle_path, bundle_file )
|
270
|
-
|
271
|
-
bundle_info = bundle_info( bundle_path )
|
272
|
-
|
273
|
-
@info[bundle_name.to_sym] = bundle_info
|
274
|
-
|
275
|
-
bundle_src = File.read( bundle_file_path )
|
276
|
-
|
277
|
-
module_ns = Plugins.bundle_loader( {
|
278
|
-
:bundle_path => bundle_path,
|
279
|
-
:bundle_name => bundle_name,
|
280
|
-
:bundle_info => bundle_info,
|
281
|
-
:plugin_manager => self,
|
282
|
-
:src_path => bundle_file_path,
|
283
|
-
:src => bundle_src
|
284
|
-
} )
|
285
|
-
|
286
|
-
module_ns.constants.each do |module_const_name|
|
287
|
-
module_const = module_ns.const_get( module_const_name )
|
288
|
-
if module_const.class == Class
|
289
|
-
bundle_type = module_const.bundle_type
|
290
|
-
if [:Servlet, :Plugin, :GUIPlugin].include? bundle_type
|
291
|
-
bundle_inst = module_const.new( bundle_name, bundle_info, bundle_path, self )
|
292
|
-
bundle_inst.register( bundle_name ) if [ :Plugin, :GUIPlugin ].include?( bundle_type )
|
293
|
-
break
|
294
|
-
else
|
295
|
-
warn "Can't init class: #{module_const.to_s}"
|
296
|
-
break
|
297
|
-
end
|
298
|
-
else
|
299
|
-
warn "Invalid module_const.class: #{module_const.class.inspect}"
|
300
|
-
end
|
42
|
+
# Registers alias name for a plugin bundle.
|
43
|
+
def register_alias( bundle_name, alias_name )
|
44
|
+
if @aliases.has_key?( alias_name.to_sym )
|
45
|
+
warn "Alias already taken: #{alias_name.inspect}"
|
46
|
+
else
|
47
|
+
@aliases[ alias_name ] = bundle_name.to_sym
|
301
48
|
end
|
302
49
|
end
|
303
50
|
|
@@ -315,20 +62,44 @@ module RSence
|
|
315
62
|
inst.init if inst.respond_to? :init and not inst.inited
|
316
63
|
@registry[ bundle_name ] = inst
|
317
64
|
if inst.respond_to?( :match ) and ( inst.respond_to?( :get ) or inst.respond_to?( :post ) )
|
318
|
-
puts " --- servlet: #{bundle_name.inspect}, #{inst.respond_to?(:match)}, #{inst.post}" if bundle_name == :welcome
|
319
65
|
@servlets.push( bundle_name )
|
320
66
|
end
|
321
67
|
end
|
322
68
|
end
|
323
69
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
70
|
+
def callable?( plugin_name, method_name )
|
71
|
+
return false if @deps.category?( plugin_name )
|
72
|
+
return false unless @registry.has_key?( plugin_name )
|
73
|
+
plugin = @registry[plugin_name]
|
74
|
+
return false unless plugin.respond_to?( method_name )
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
|
78
|
+
# Calls the method +method_name+ with args +args+ of the plugin +plugin_name+.
|
79
|
+
# Returns false, if no such plugin or method exists.
|
80
|
+
def call( plugin_name, method_name, *args )
|
81
|
+
plugin_name = plugin_name.to_sym
|
82
|
+
if callable?( plugin_name, method_name )
|
83
|
+
begin
|
84
|
+
return @registry[ plugin_name ].send( method_name, *args )
|
85
|
+
rescue => e
|
86
|
+
plugin_error(
|
87
|
+
e,
|
88
|
+
"RSence::PluginManager.call error",
|
89
|
+
"plugin_name: #{plugin_name.inspect}, method_name: #{method_name.inspect}",
|
90
|
+
plugin_name
|
91
|
+
)
|
92
|
+
end
|
93
|
+
elsif @deps.category?( plugin_name )
|
94
|
+
warn "Warning! Tried to call category: #{plugin_name.inpsect}"
|
95
|
+
elsif not @registry.has_key?( plugin_name )
|
96
|
+
warn "Warning! No such plugin: #{plugin_name.inspect}"
|
97
|
+
elsif not @registry[ plugin_name ].respond_to?( method_name )
|
98
|
+
warn "Warning! Plugin: #{plugin_name.inspect} does not respond to #{method_name.inspect}"
|
330
99
|
end
|
100
|
+
return false
|
331
101
|
end
|
102
|
+
alias run_plugin call
|
332
103
|
|
333
104
|
# Prettier error handling.
|
334
105
|
def plugin_error( e, err_location, err_location_descr, eval_repl=false )
|
@@ -392,50 +163,6 @@ module RSence
|
|
392
163
|
end
|
393
164
|
end
|
394
165
|
|
395
|
-
# Delegates +method_name+ with +args+ to any loaded
|
396
|
-
# plugin that responds to the method.
|
397
|
-
def delegate( method_name, *args )
|
398
|
-
@registry.each do | plugin_name, plugin |
|
399
|
-
if plugin.respond_to?( method_name )
|
400
|
-
begin
|
401
|
-
plugin.send( method_name, *args )
|
402
|
-
rescue => e
|
403
|
-
plugin_error(
|
404
|
-
e,
|
405
|
-
"RSence::PluginManager.delegate error",
|
406
|
-
"plugin_name: #{plugin_name.inspect}, method_name: #{method_name.inspect}",
|
407
|
-
plugin_name
|
408
|
-
)
|
409
|
-
end
|
410
|
-
end
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
# Delegates the +flush+ and +close+ methods to any
|
415
|
-
# loaded plugins, in that order.
|
416
|
-
def shutdown
|
417
|
-
delegate( :flush )
|
418
|
-
delegate( :close )
|
419
|
-
end
|
420
|
-
|
421
|
-
# Calls the method +method_name+ with args +args+ of the plugin +plugin_name+.
|
422
|
-
# Returns false, if no such plugin or method exists.
|
423
|
-
def call( plugin_name, method_name, *args )
|
424
|
-
plugin_name = plugin_name.to_sym
|
425
|
-
if @registry.has_key?( plugin_name )
|
426
|
-
if @registry[ plugin_name ].respond_to?( method_name )
|
427
|
-
return @registry[ plugin_name ].send( method_name, *args )
|
428
|
-
else
|
429
|
-
puts "No method #{method_name.inspect} for plugin #{plugin_name.inspect}"
|
430
|
-
return false
|
431
|
-
end
|
432
|
-
else
|
433
|
-
puts "No such plugin: #{plugin_name.inspect}"
|
434
|
-
return false
|
435
|
-
end
|
436
|
-
end
|
437
|
-
alias run_plugin call
|
438
|
-
|
439
166
|
# Calls the servlet that matches the +req_type+ and +req.fullpath+ with
|
440
167
|
# the highest score.
|
441
168
|
def match_servlet( req_type, req, resp, session )
|
@@ -458,5 +185,410 @@ module RSence
|
|
458
185
|
end
|
459
186
|
return false
|
460
187
|
end
|
188
|
+
|
189
|
+
# Delegates +method_name+ with +args+ to any loaded
|
190
|
+
# plugin that responds to the method.
|
191
|
+
def delegate( method_name, *args )
|
192
|
+
@deps.list.each do |plugin_name|
|
193
|
+
call( plugin_name, method_name, *args ) if callable?( plugin_name, method_name )
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Reverse delegate +method_name+ with +args+ to any loaded
|
198
|
+
# plugin that responds to the method.
|
199
|
+
def delegate_reverse( method_name, *args )
|
200
|
+
@deps.list.reverse.each do |plugin_name|
|
201
|
+
call( plugin_name, method_name, *args ) if callable?( plugin_name, method_name )
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Delegates the +flush+ and +close+ methods to any
|
206
|
+
# loaded plugins, in that order.
|
207
|
+
def shutdown
|
208
|
+
@transporter.online = false
|
209
|
+
@deps.list.reverse.each do |bundle_name|
|
210
|
+
unload_bundle( bundle_name )
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Finds the most recent file in the path
|
215
|
+
def most_recent( bundle_path, newest_date=0 )
|
216
|
+
path_date = File.stat( bundle_path ).mtime.to_i
|
217
|
+
is_dir = File.directory?( bundle_path )
|
218
|
+
if path_date > newest_date and not is_dir
|
219
|
+
newest_date = path_date
|
220
|
+
end
|
221
|
+
if is_dir
|
222
|
+
Dir.entries( bundle_path ).each do |entry_name|
|
223
|
+
next if entry_name[0].chr == '.'
|
224
|
+
full_path = File.join( bundle_path, entry_name )
|
225
|
+
unless File.directory?( full_path )
|
226
|
+
has_dot = entry_name.include?('.')
|
227
|
+
next unless has_dot
|
228
|
+
is_src_file = ['yaml','rb'].include?( entry_name.split('.')[-1] )
|
229
|
+
next unless is_src_file
|
230
|
+
end
|
231
|
+
newest_date = most_recent( full_path, newest_date )
|
232
|
+
end
|
233
|
+
end
|
234
|
+
return newest_date
|
235
|
+
end
|
236
|
+
|
237
|
+
# Gets plugin information
|
238
|
+
def bundle_info( bundle_path, bundle_name, src_file )
|
239
|
+
|
240
|
+
# Default bundle information
|
241
|
+
info = {
|
242
|
+
|
243
|
+
# The human-readable product name of the package
|
244
|
+
:title => bundle_name.to_s.capitalize,
|
245
|
+
|
246
|
+
# The human-readable version of the package
|
247
|
+
:version => '0.0.0',
|
248
|
+
|
249
|
+
# A brief description of the package (rdoc formatting supported)
|
250
|
+
:description => 'No Description',
|
251
|
+
|
252
|
+
# A flag (when false) prevents the plugin from automatically reload when changed.
|
253
|
+
:reloadable => true,
|
254
|
+
|
255
|
+
# System version requirement.
|
256
|
+
# NOTE: Has no effect yet!
|
257
|
+
:sys_version => '>= 1.0.0',
|
258
|
+
|
259
|
+
# Dependency, by default the system category (built-in plugins).
|
260
|
+
# A nil ( "~" in yaml ) value means no dependencies.
|
261
|
+
:depends_on => :system,
|
262
|
+
|
263
|
+
# Optional, name of category. The built-in plugins are :system
|
264
|
+
:category => nil,
|
265
|
+
|
266
|
+
# Optional, name of plugin to replace
|
267
|
+
# NOTE: Has no effect yet!
|
268
|
+
:replaces => nil,
|
269
|
+
|
270
|
+
# Optional, reverse dependency. Loads before the prepended plugin(category).
|
271
|
+
# NOTE: Doesn't support packages yet!
|
272
|
+
:prepends => nil
|
273
|
+
|
274
|
+
}
|
275
|
+
|
276
|
+
# Merge info.yaml data into info
|
277
|
+
info_path = File.join( bundle_path, 'info.yaml' )
|
278
|
+
if File.exists?( info_path )
|
279
|
+
info_yaml = YAML.load( File.read( info_path ) )
|
280
|
+
info_yaml.each do |info_key,info_value|
|
281
|
+
info[ info_key.to_sym ] = info_value
|
282
|
+
end
|
283
|
+
else
|
284
|
+
warn "Expected info.yaml, using defaults:"
|
285
|
+
warn " #{info_path}"
|
286
|
+
end
|
287
|
+
|
288
|
+
@deps.set_deps( bundle_name, info[:depends_on] )
|
289
|
+
if info[:category]
|
290
|
+
if info[:category].class == Symbol
|
291
|
+
@deps.add_category( info[:category] ) unless @deps.category?( info[:category] )
|
292
|
+
@deps.set_deps( info[:category], bundle_name )
|
293
|
+
else
|
294
|
+
warn "Invalid category: #{info[:category].inspect}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
if info[:prepends]
|
298
|
+
if info[:prepends].class == Array
|
299
|
+
info[:prepends].each do |prep|
|
300
|
+
@deps.set_deps( prep, bundle_name )
|
301
|
+
end
|
302
|
+
else
|
303
|
+
@deps.set_deps( info[:prepends], bundle_name )
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Extra information, not overrideable in info.yaml
|
308
|
+
|
309
|
+
# Path of bundle
|
310
|
+
info[:path] = bundle_path
|
311
|
+
|
312
|
+
# Name of bundle
|
313
|
+
info[:name] = bundle_name
|
314
|
+
|
315
|
+
# Full path of source file
|
316
|
+
info[:src_file] = src_file
|
317
|
+
|
318
|
+
# Timestamp of last changed file
|
319
|
+
info[:last_changed] = most_recent( bundle_path )
|
320
|
+
|
321
|
+
# ..however, don't accept future timestamps:
|
322
|
+
time_now = Time.now.to_i
|
323
|
+
info[:last_changed] = time_now if info[:last_changed] > time_now
|
324
|
+
|
325
|
+
return info
|
326
|
+
end
|
327
|
+
|
328
|
+
# Loads a plugin bundle.
|
329
|
+
def load_bundle( name )
|
330
|
+
|
331
|
+
if @deps.unresolved?(name)
|
332
|
+
warn "Warning: Bundle #{name} has unmet dependencies."
|
333
|
+
return
|
334
|
+
end
|
335
|
+
|
336
|
+
if @registry.has_key?( name )
|
337
|
+
warn "Warning: Bundle #{name} already loaded."
|
338
|
+
return
|
339
|
+
end
|
340
|
+
puts "Loading bundle: #{name.inspect}" if RSence.args[:debug]
|
341
|
+
|
342
|
+
info = @info[ name ]
|
343
|
+
|
344
|
+
path = info[:path]
|
345
|
+
src_file = info[:src_file]
|
346
|
+
|
347
|
+
bundle_src = File.read( src_file )
|
348
|
+
|
349
|
+
module_ns = Plugins.bundle_loader( {
|
350
|
+
:bundle_path => path,
|
351
|
+
:bundle_name => name,
|
352
|
+
:bundle_info => info,
|
353
|
+
:plugin_manager => self,
|
354
|
+
:src_path => src_file,
|
355
|
+
:src => bundle_src
|
356
|
+
} )
|
357
|
+
|
358
|
+
module_ns.constants.each do |module_const_name|
|
359
|
+
module_const = module_ns.const_get( module_const_name )
|
360
|
+
if module_const.class == Class
|
361
|
+
type = module_const.bundle_type
|
362
|
+
if [:Servlet, :Plugin, :GUIPlugin].include? type
|
363
|
+
bundle_inst = module_const.new( name, info, path, self )
|
364
|
+
bundle_inst.register( name ) if [ :Plugin, :GUIPlugin ].include?( type )
|
365
|
+
break
|
366
|
+
else
|
367
|
+
warn "Can't init class: #{module_const.to_s}"
|
368
|
+
break
|
369
|
+
end
|
370
|
+
else
|
371
|
+
warn "Invalid module_const.class: #{module_const.class.inspect}"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# loads all bundles found in order of dependency
|
377
|
+
def load_bundles
|
378
|
+
@deps.list.each do |name|
|
379
|
+
load_bundle( name ) if @deps.loadable?( name )
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# If a bundle is found, set its dependencies etc
|
384
|
+
def bundle_found( bundle_path, bundle_name, src_file )
|
385
|
+
@info[ bundle_name ] = bundle_info( bundle_path, bundle_name, src_file )
|
386
|
+
end
|
387
|
+
|
388
|
+
# Returns false, if the plugin directory isn't valid.
|
389
|
+
# Returns [bundle_path, src_file] otherwise.
|
390
|
+
def valid_plugindir?( path, bundle_name )
|
391
|
+
return false if bundle_name[0].chr == '.'
|
392
|
+
bundle_path = File.expand_path( File.join( path, bundle_name ) )
|
393
|
+
return false unless File.directory?( bundle_path )
|
394
|
+
bundle_file = bundle_name+'.rb'
|
395
|
+
src_file = File.join( bundle_path, bundle_file )
|
396
|
+
if not File.exists?( src_file )
|
397
|
+
bundle_file = 'main.rb'
|
398
|
+
src_file = File.join( bundle_path, bundle_file )
|
399
|
+
return false unless File.exists?( src_file )
|
400
|
+
end
|
401
|
+
return [ bundle_path, src_file ]
|
402
|
+
end
|
403
|
+
|
404
|
+
# Returns true, if the bundle is disabled
|
405
|
+
def is_disabled?( bundle_path )
|
406
|
+
File.exists?( File.join( bundle_path, 'disabled' ) )
|
407
|
+
end
|
408
|
+
|
409
|
+
# Returns true, if the bundle is loaded.
|
410
|
+
def is_loaded?( bundle_name )
|
411
|
+
@registry.has_key?( bundle_name )
|
412
|
+
end
|
413
|
+
|
414
|
+
# Scans a directory of plugins, calls +load_plugin+ for bundles that match
|
415
|
+
# the definition of a plugin bundle.
|
416
|
+
# - Skips bundles starting with a dot
|
417
|
+
# - Skips bundles without a ruby source file with the same
|
418
|
+
# name as the directory (plus '.rb').
|
419
|
+
# - Skips bundles containing a file or directory named 'disabled'
|
420
|
+
def scan_plugindir( path )
|
421
|
+
bundles_found = []
|
422
|
+
Dir.entries(path).each do |bundle_name|
|
423
|
+
bundle_status = valid_plugindir?( path, bundle_name )
|
424
|
+
if bundle_status
|
425
|
+
(bundle_path, src_file) = bundle_status
|
426
|
+
bundles_found.push( [bundle_path, bundle_name.to_sym, src_file] )
|
427
|
+
end
|
428
|
+
end
|
429
|
+
return bundles_found
|
430
|
+
end
|
431
|
+
|
432
|
+
# Top-level method for scanning all plugin directories.
|
433
|
+
# Clears previously loaded plugins.
|
434
|
+
def scan_plugins
|
435
|
+
@registry = {} # bundle_name => bundle_instance mapping
|
436
|
+
@info = {} # bundle_name => bundle_info mapping
|
437
|
+
@aliases = {} # bundle_alias => bundle_name mapping
|
438
|
+
@servlets = [] # bundle_name list of Servlet class instances
|
439
|
+
bundles_found = []
|
440
|
+
@plugin_paths.each do |path|
|
441
|
+
next unless File.directory? path
|
442
|
+
bundles_found += scan_plugindir( path )
|
443
|
+
end
|
444
|
+
bundles_found.each do |bundle_path, bundle_name, src_file|
|
445
|
+
unless is_disabled?( bundle_path )
|
446
|
+
bundle_found( bundle_path, bundle_name, src_file )
|
447
|
+
end
|
448
|
+
end
|
449
|
+
load_bundles
|
450
|
+
delegate( :open )
|
451
|
+
end
|
452
|
+
|
453
|
+
# Unloads the plugin bundle named +bundle_name+
|
454
|
+
def unload_bundle( bundle_name )
|
455
|
+
if @registry.has_key?( bundle_name )
|
456
|
+
unload_order = @deps.del_order( bundle_name )
|
457
|
+
unload_order.each do |unload_dep|
|
458
|
+
unload_bundle( unload_dep ) unless unload_dep == bundle_name
|
459
|
+
end
|
460
|
+
puts "Unloading bundle: #{bundle_name.inspect}" if RSence.args[:debug]
|
461
|
+
@deps.del_item( bundle_name )
|
462
|
+
online_status = @transporter.online?
|
463
|
+
@transporter.online = false
|
464
|
+
call( bundle_name, :flush )
|
465
|
+
call( bundle_name, :close )
|
466
|
+
@registry.delete( bundle_name )
|
467
|
+
@aliases.each do |a_name,b_name|
|
468
|
+
if b_name == bundle_name
|
469
|
+
@aliases.delete( a_name )
|
470
|
+
end
|
471
|
+
end
|
472
|
+
if @servlets.include?( bundle_name )
|
473
|
+
@servlets.delete( bundle_name )
|
474
|
+
end
|
475
|
+
if @info.include?( bundle_name )
|
476
|
+
@info.delete( bundle_name )
|
477
|
+
end
|
478
|
+
@transporter.online = online_status
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
# Returns true, if a plugin bundle has changed.
|
483
|
+
# Only compares timestamp, not checksum.
|
484
|
+
def plugin_changed?( plugin_name )
|
485
|
+
info = @info[plugin_name]
|
486
|
+
last_changed = info[:last_changed]
|
487
|
+
newest_change = most_recent( info[:path], last_changed )
|
488
|
+
return last_changed < newest_change
|
489
|
+
end
|
490
|
+
|
491
|
+
# Logs and speaks the message
|
492
|
+
def say( message )
|
493
|
+
puts message
|
494
|
+
if RSence.args[:say]
|
495
|
+
Thread.new do
|
496
|
+
Thread.pass
|
497
|
+
system(%{say "#{message.gsub('"','')}"})
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
# Checks for changed plugin bundles and unloads/loads/reloads them accordingly.
|
503
|
+
def changed_plugins!
|
504
|
+
bundles_found = []
|
505
|
+
@plugin_paths.each do |path|
|
506
|
+
bundles_found += scan_plugindir( path )
|
507
|
+
end
|
508
|
+
bundle_names_found = []
|
509
|
+
bundles_found.each do |bundle_path, bundle_name, src_file|
|
510
|
+
bundle_names_found.push( bundle_name )
|
511
|
+
is_loaded = is_loaded?( bundle_name )
|
512
|
+
if is_loaded and is_disabled?( bundle_path )
|
513
|
+
# bundle already loaded but disabled now, should be unloaded:
|
514
|
+
unload_bundle( bundle_name )
|
515
|
+
say( "Unloaded #{bundle_name}." )
|
516
|
+
elsif is_loaded and plugin_changed?( bundle_name )
|
517
|
+
# bundle changed, should be reloaded:
|
518
|
+
unload_bundle( bundle_name )
|
519
|
+
unless @info.has_key?( bundle_name ) and not plugin_changed?( bundle_name )
|
520
|
+
@info[bundle_name] = bundle_info( bundle_path, bundle_name, src_file )
|
521
|
+
end
|
522
|
+
if @deps.resolved?( bundle_name )
|
523
|
+
load_bundle( bundle_name )
|
524
|
+
say( "Reloaded #{bundle_name}." )
|
525
|
+
end
|
526
|
+
elsif not is_loaded
|
527
|
+
# bundle not loaded, should be loaded:
|
528
|
+
unless @info.has_key?( bundle_name ) and not plugin_changed?( bundle_name )
|
529
|
+
@info[bundle_name] = bundle_info( bundle_path, bundle_name, src_file )
|
530
|
+
end
|
531
|
+
if @deps.resolved?( bundle_name )
|
532
|
+
load_bundle( bundle_name )
|
533
|
+
say( "Loaded #{bundle_name}." )
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
bundles_missing = @info.keys - bundle_names_found
|
538
|
+
bundles_missing.each do |bundle_name|
|
539
|
+
say( "#{bundle_name} deleted, unloading.." )
|
540
|
+
unload_bundle( bundle_name )
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
# Initialize with a list of directories as plugin_paths.
|
545
|
+
# It's an array containing all plugin directories to scan.
|
546
|
+
def initialize( plugin_paths, transporter=nil,
|
547
|
+
autoreload=false, name_prefix=false,
|
548
|
+
resolved_deps=[], resolved_categories={} )
|
549
|
+
if transporter
|
550
|
+
@transporter = transporter
|
551
|
+
@sessions = transporter.sessions
|
552
|
+
end
|
553
|
+
@name_prefix = name_prefix
|
554
|
+
@plugin_paths = plugin_paths
|
555
|
+
@deps = Dependencies.new( resolved_deps, resolved_categories )
|
556
|
+
puts "Loading #{name_prefix+' ' if name_prefix}plugins..." if RSence.args[:verbose]
|
557
|
+
scan_plugins
|
558
|
+
puts %{Plugins #{"of #{name_prefix} " if name_prefix}loaded.} if RSence.args[:verbose]
|
559
|
+
if autoreload
|
560
|
+
@thr = Thread.new do
|
561
|
+
Thread.pass
|
562
|
+
while true
|
563
|
+
begin
|
564
|
+
changed_plugins!
|
565
|
+
rescue => e
|
566
|
+
warn e.inspect
|
567
|
+
end
|
568
|
+
sleep 3
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
461
573
|
end
|
462
574
|
end
|
575
|
+
|
576
|
+
|
577
|
+
|
578
|
+
|
579
|
+
|
580
|
+
|
581
|
+
|
582
|
+
|
583
|
+
|
584
|
+
|
585
|
+
|
586
|
+
|
587
|
+
|
588
|
+
|
589
|
+
|
590
|
+
|
591
|
+
|
592
|
+
|
593
|
+
|
594
|
+
|