rsence 2.0.0.8.pre → 2.0.0.9.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|