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.
@@ -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
+