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.
@@ -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 false
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 false
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 directory and 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( @path, 'db' )
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
- # Initialize with a list of directories as plugin_paths.
25
- # It's an array containing all plugin directories to scan.
26
- def initialize( plugin_paths, transporter=nil, autoreload=false, name_prefix=false )
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
- # Checks for changed plugin bundles and unloads/loads/reloads them accordingly.
63
- def changed_plugins!
64
- @plugin_paths.each do |path|
65
- next unless File.directory? path
66
- Dir.entries(path).each do |bundle_name|
67
- next if bundle_name =~ /&\./
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
- # Registers alias name for a plugin bundle.
325
- def register_alias( bundle_name, alias_name )
326
- if @aliases.has_key?( alias_name.to_sym )
327
- warn "Alias already taken: #{alias_name.inspect}"
328
- else
329
- @aliases[ alias_name ] = bundle_name.to_sym
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
+