rsence-pre 2.1.0.1.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/.yardopts +10 -0
- data/INSTALL.rdoc +330 -0
- data/LICENSE.txt +622 -0
- data/README.rdoc +98 -0
- data/VERSION +1 -0
- data/bin/rsence +25 -0
- data/bin/rsence-pre +25 -0
- data/conf/default_conf.yaml +346 -0
- data/conf/default_strings.yaml +76 -0
- data/conf/rsence_command_strings.yaml +444 -0
- data/docs/ExampleGuiPlugin.rdoc +193 -0
- data/docs/JavascriptBundles.rdoc +0 -0
- data/docs/PluginBundleInfo.rdoc +173 -0
- data/docs/PluginBundles.rdoc +96 -0
- data/docs/Values.rdoc +163 -0
- data/js/comm/autosync/autosync.js +17 -0
- data/js/comm/autosync/js.inc +0 -0
- data/js/comm/comm.js +203 -0
- data/js/comm/js.inc +0 -0
- data/js/comm/jsloader/js.inc +0 -0
- data/js/comm/jsloader/jsloader.js +112 -0
- data/js/comm/queue/js.inc +0 -0
- data/js/comm/queue/queue.js +184 -0
- data/js/comm/session/js.inc +0 -0
- data/js/comm/session/session.js +52 -0
- data/js/comm/sessionwatcher/js.inc +0 -0
- data/js/comm/sessionwatcher/sessionwatcher.js +44 -0
- data/js/comm/transporter/js.inc +0 -0
- data/js/comm/transporter/transporter.js +261 -0
- data/js/comm/urlresponder/js.inc +0 -0
- data/js/comm/urlresponder/urlresponder.js +149 -0
- data/js/comm/values/js.inc +0 -0
- data/js/comm/values/values.js +433 -0
- data/js/controls/button/button.js +72 -0
- data/js/controls/button/js.inc +0 -0
- data/js/controls/button/themes/bright/button.css +89 -0
- data/js/controls/button/themes/bright/button.html +7 -0
- data/js/controls/button/themes/bright/button_parts1-ie6.gif +0 -0
- data/js/controls/button/themes/bright/button_parts1.png +0 -0
- data/js/controls/button/themes/default/button.css +89 -0
- data/js/controls/button/themes/default/button.html +7 -0
- data/js/controls/button/themes/default/button_parts1-ie6.gif +0 -0
- data/js/controls/button/themes/default/button_parts1.png +0 -0
- data/js/controls/checkbox/checkbox.js +49 -0
- data/js/controls/checkbox/js.inc +0 -0
- data/js/controls/checkbox/themes/default/checkbox.css +69 -0
- data/js/controls/checkbox/themes/default/checkbox.html +5 -0
- data/js/controls/checkbox/themes/default/checkbox_parts1-ie6.gif +0 -0
- data/js/controls/checkbox/themes/default/checkbox_parts1.png +0 -0
- data/js/controls/dialogs/alert_sheet/alert_sheet.js +63 -0
- data/js/controls/dialogs/alert_sheet/js.inc +0 -0
- data/js/controls/dialogs/confirm_sheet/confirm_sheet.js +37 -0
- data/js/controls/dialogs/confirm_sheet/js.inc +0 -0
- data/js/controls/dialogs/sheet/js.inc +0 -0
- data/js/controls/dialogs/sheet/sheet.js +84 -0
- data/js/controls/dialogs/sheet/themes/default/sheet.css +64 -0
- data/js/controls/dialogs/sheet/themes/default/sheet.html +14 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_bg-ie6.gif +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_bg.png +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_dim-ie6.gif +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_dim.png +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_parts1-ie6.gif +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_parts1.png +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_parts2-ie6.gif +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_parts2.png +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_warning-ie6.gif +0 -0
- data/js/controls/dialogs/sheet/themes/default/sheet_warning.png +0 -0
- data/js/controls/imageview/imageview.js +109 -0
- data/js/controls/imageview/js.inc +0 -0
- data/js/controls/imageview/themes/default/blank.gif +0 -0
- data/js/controls/passwordcontrol/js.inc +0 -0
- data/js/controls/passwordcontrol/passwordcontrol.js +23 -0
- data/js/controls/passwordcontrol/themes/default/passwordcontrol.css +0 -0
- data/js/controls/passwordcontrol/themes/default/passwordcontrol.html +18 -0
- data/js/controls/progress/progressbar/js.inc +0 -0
- data/js/controls/progress/progressbar/progressbar.js +40 -0
- data/js/controls/progress/progressbar/themes/default/progressbar.css +16 -0
- data/js/controls/progress/progressbar/themes/default/progressbar.html +2 -0
- data/js/controls/progress/progressindicator/js.inc +0 -0
- data/js/controls/progress/progressindicator/progressindicator.js +44 -0
- data/js/controls/radiobutton/js.inc +0 -0
- data/js/controls/radiobutton/radiobutton.js +43 -0
- data/js/controls/radiobutton/themes/default/radiobutton.css +69 -0
- data/js/controls/radiobutton/themes/default/radiobutton.html +5 -0
- data/js/controls/radiobutton/themes/default/radiobutton_parts1-ie6.gif +0 -0
- data/js/controls/radiobutton/themes/default/radiobutton_parts1.png +0 -0
- data/js/controls/sliders/slider/js.inc +0 -0
- data/js/controls/sliders/slider/slider.js +357 -0
- data/js/controls/sliders/slider/themes/default/hslider_tracks-ie6.gif +0 -0
- data/js/controls/sliders/slider/themes/default/hslider_tracks.png +0 -0
- data/js/controls/sliders/slider/themes/default/slider.css +108 -0
- data/js/controls/sliders/slider/themes/default/slider.html +5 -0
- data/js/controls/sliders/slider/themes/default/slider_thumbs-ie6.gif +0 -0
- data/js/controls/sliders/slider/themes/default/slider_thumbs.png +0 -0
- data/js/controls/sliders/vslider/js.inc +0 -0
- data/js/controls/sliders/vslider/themes/default/vslider.css +52 -0
- data/js/controls/sliders/vslider/themes/default/vslider.html +5 -0
- data/js/controls/sliders/vslider/themes/default/vslider_tracks-ie6.gif +0 -0
- data/js/controls/sliders/vslider/themes/default/vslider_tracks.png +0 -0
- data/js/controls/sliders/vslider/vslider.js +41 -0
- data/js/controls/stepper/js.inc +0 -0
- data/js/controls/stepper/stepper.js +213 -0
- data/js/controls/stepper/themes/default/stepper-ie6.gif +0 -0
- data/js/controls/stepper/themes/default/stepper.css +14 -0
- data/js/controls/stepper/themes/default/stepper.html +2 -0
- data/js/controls/stepper/themes/default/stepper.png +0 -0
- data/js/controls/stringview/js.inc +0 -0
- data/js/controls/stringview/stringview.js +49 -0
- data/js/controls/stringview/themes/default/stringview.css +8 -0
- data/js/controls/stringview/themes/default/stringview.html +1 -0
- data/js/controls/tab/js.inc +0 -0
- data/js/controls/tab/tab.js +280 -0
- data/js/controls/tab/themes/bright/tab.css +76 -0
- data/js/controls/tab/themes/bright/tab.html +6 -0
- data/js/controls/tab/themes/bright/tab_bg_color-ie6.gif +0 -0
- data/js/controls/tab/themes/bright/tab_bg_color.png +0 -0
- data/js/controls/tab/themes/bright/tab_border_pattern-ie6.gif +0 -0
- data/js/controls/tab/themes/bright/tab_border_pattern.png +0 -0
- data/js/controls/tab/themes/bright/tab_parts1-ie6.gif +0 -0
- data/js/controls/tab/themes/bright/tab_parts1.png +0 -0
- data/js/controls/tab/themes/default/tab.css +77 -0
- data/js/controls/tab/themes/default/tab.html +6 -0
- data/js/controls/tab/themes/default/tab_bg_color-ie6.gif +0 -0
- data/js/controls/tab/themes/default/tab_bg_color.png +0 -0
- data/js/controls/tab/themes/default/tab_border_pattern-ie6.gif +0 -0
- data/js/controls/tab/themes/default/tab_border_pattern.png +0 -0
- data/js/controls/tab/themes/default/tab_parts1-ie6.gif +0 -0
- data/js/controls/tab/themes/default/tab_parts1.png +0 -0
- data/js/controls/textarea/js.inc +0 -0
- data/js/controls/textarea/textarea.js +24 -0
- data/js/controls/textarea/themes/default/textarea.css +21 -0
- data/js/controls/textarea/themes/default/textarea.html +18 -0
- data/js/controls/textcontrol/js.inc +0 -0
- data/js/controls/textcontrol/textcontrol.js +374 -0
- data/js/controls/textcontrol/themes/default/textcontrol.css +107 -0
- data/js/controls/textcontrol/themes/default/textcontrol.html +18 -0
- data/js/controls/textcontrol/themes/default/textcontrol_parts1-ie6.gif +0 -0
- data/js/controls/textcontrol/themes/default/textcontrol_parts1.png +0 -0
- data/js/controls/textcontrol/themes/default/textcontrol_parts2-ie6.gif +0 -0
- data/js/controls/textcontrol/themes/default/textcontrol_parts2.png +0 -0
- data/js/controls/textcontrol/themes/default/textcontrol_parts3-ie6.gif +0 -0
- data/js/controls/textcontrol/themes/default/textcontrol_parts3.png +0 -0
- data/js/controls/uploader/js.inc +0 -0
- data/js/controls/uploader/themes/default/upload_progress.gif +0 -0
- data/js/controls/uploader/themes/default/uploader.css +108 -0
- data/js/controls/uploader/themes/default/uploader.html +27 -0
- data/js/controls/uploader/uploader.js +154 -0
- data/js/controls/validatorview/js.inc +0 -0
- data/js/controls/validatorview/themes/default/validator-ie6.gif +0 -0
- data/js/controls/validatorview/themes/default/validator.png +0 -0
- data/js/controls/validatorview/themes/default/validatorview.css +0 -0
- data/js/controls/validatorview/themes/default/validatorview.html +0 -0
- data/js/controls/validatorview/validatorview.js +62 -0
- data/js/controls/window/js.inc +0 -0
- data/js/controls/window/themes/default/window.css +219 -0
- data/js/controls/window/themes/default/window.html +17 -0
- data/js/controls/window/themes/default/window_bg_active-ie6.gif +0 -0
- data/js/controls/window/themes/default/window_bg_active.png +0 -0
- data/js/controls/window/themes/default/window_bg_inactive-ie6.gif +0 -0
- data/js/controls/window/themes/default/window_bg_inactive.png +0 -0
- data/js/controls/window/themes/default/window_buttons-ie6.gif +0 -0
- data/js/controls/window/themes/default/window_buttons.png +0 -0
- data/js/controls/window/themes/default/window_parts1-ie6.gif +0 -0
- data/js/controls/window/themes/default/window_parts1.png +0 -0
- data/js/controls/window/themes/default/window_parts2-ie6.gif +0 -0
- data/js/controls/window/themes/default/window_parts2.png +0 -0
- data/js/controls/window/window.js +286 -0
- data/js/core/class/class.js +318 -0
- data/js/core/class/js.inc +0 -0
- data/js/core/elem/elem.js +1383 -0
- data/js/core/elem/js.inc +0 -0
- data/js/core/event/event.js +153 -0
- data/js/core/event/js.inc +0 -0
- data/js/core/iefix/ie_css_element.htc +5 -0
- data/js/core/iefix/ie_css_style.htc +5 -0
- data/js/core/iefix/iefix.js +359 -0
- data/js/core/iefix/js.inc +0 -0
- data/js/core/rsence_ns/js.inc +0 -0
- data/js/core/rsence_ns/rsence_ns.js +21 -0
- data/js/datetime/calendar/calendar.js +198 -0
- data/js/datetime/calendar/js.inc +0 -0
- data/js/datetime/calendar/themes/default/calendar.css +108 -0
- data/js/datetime/calendar/themes/default/calendar.html +9 -0
- data/js/datetime/calendar/themes/default/calendar_arrows-ie6.gif +0 -0
- data/js/datetime/calendar/themes/default/calendar_arrows.png +0 -0
- data/js/datetime/datetimevalue/datetimevalue.js +247 -0
- data/js/datetime/datetimevalue/js.inc +0 -0
- data/js/datetime/timesheet/js.inc +0 -0
- data/js/datetime/timesheet/themes/default/timesheet.css +30 -0
- data/js/datetime/timesheet/themes/default/timesheet.html +2 -0
- data/js/datetime/timesheet/timesheet.js +183 -0
- data/js/datetime/timesheet_item/js.inc +0 -0
- data/js/datetime/timesheet_item/themes/default/timesheet_item.css +42 -0
- data/js/datetime/timesheet_item/themes/default/timesheet_item.html +8 -0
- data/js/datetime/timesheet_item/timesheet_item.js +248 -0
- data/js/datetime/timesheet_item_edit/js.inc +0 -0
- data/js/datetime/timesheet_item_edit/timesheet_item_edit.js +274 -0
- data/js/foundation/application/application.js +208 -0
- data/js/foundation/application/js.inc +0 -0
- data/js/foundation/control/control.js +339 -0
- data/js/foundation/control/controldefaults/controldefaults.js +56 -0
- data/js/foundation/control/controldefaults/js.inc +0 -0
- data/js/foundation/control/dummyvalue/dummyvalue.js +51 -0
- data/js/foundation/control/dummyvalue/js.inc +0 -0
- data/js/foundation/control/dyncontrol/dyncontrol.js +500 -0
- data/js/foundation/control/dyncontrol/js.inc +0 -0
- data/js/foundation/control/dyncontrol/themes/default/dyncontrol.css +0 -0
- data/js/foundation/control/dyncontrol/themes/default/dyncontrol.html +0 -0
- data/js/foundation/control/eventresponder/eventresponder.js +750 -0
- data/js/foundation/control/eventresponder/js.inc +0 -0
- data/js/foundation/control/js.inc +0 -0
- data/js/foundation/control/valuematrix/js.inc +0 -0
- data/js/foundation/control/valuematrix/valuematrix.js +135 -0
- data/js/foundation/control/valueresponder/js.inc +0 -0
- data/js/foundation/control/valueresponder/valueresponder.js +79 -0
- data/js/foundation/eventmanager/eventmanager.js +991 -0
- data/js/foundation/eventmanager/js.inc +0 -0
- data/js/foundation/geom/point/js.inc +0 -0
- data/js/foundation/geom/point/point.js +202 -0
- data/js/foundation/geom/rect/js.inc +0 -0
- data/js/foundation/geom/rect/rect.js +651 -0
- data/js/foundation/json_renderer/js.inc +0 -0
- data/js/foundation/json_renderer/json_renderer.js +246 -0
- data/js/foundation/system/js.inc +0 -0
- data/js/foundation/system/system.js +381 -0
- data/js/foundation/thememanager/js.inc +0 -0
- data/js/foundation/thememanager/thememanager.js +393 -0
- data/js/foundation/value/js.inc +0 -0
- data/js/foundation/value/value.js +183 -0
- data/js/foundation/view/js.inc +0 -0
- data/js/foundation/view/markupview/js.inc +0 -0
- data/js/foundation/view/markupview/markupview.js +114 -0
- data/js/foundation/view/morphanimation/js.inc +0 -0
- data/js/foundation/view/morphanimation/morphanimation.js +237 -0
- data/js/foundation/view/view.js +1812 -0
- data/js/foundation/view/viewdefaults/js.inc +0 -0
- data/js/foundation/view/viewdefaults/viewdefaults.js +26 -0
- data/js/lists/checkboxlist/checkboxlist.js +171 -0
- data/js/lists/checkboxlist/js.inc +0 -0
- data/js/lists/listitems/js.inc +0 -0
- data/js/lists/listitems/listitems.js +88 -0
- data/js/lists/propertylist/js.inc +0 -0
- data/js/lists/propertylist/propertylist.js +326 -0
- data/js/lists/radiobuttonlist/js.inc +0 -0
- data/js/lists/radiobuttonlist/radiobuttonlist.js +116 -0
- data/js/util/reloadapp/js.inc +0 -0
- data/js/util/reloadapp/reloadapp.js +152 -0
- data/js/util/reloadapp/themes/default/reloadapp_warning-ie6.gif +0 -0
- data/js/util/reloadapp/themes/default/reloadapp_warning.png +0 -0
- data/js/util/sha/js.inc +0 -0
- data/js/util/sha/sha.js +426 -0
- data/js/views/centerview/centerview.js +75 -0
- data/js/views/centerview/js.inc +0 -0
- data/js/views/inlineview/inlineview.js +15 -0
- data/js/views/inlineview/js.inc +0 -0
- data/js/views/scrollview/js.inc +0 -0
- data/js/views/scrollview/scrollview.js +40 -0
- data/lib/conf/argv.rb +850 -0
- data/lib/conf/default.rb +219 -0
- data/lib/daemon/daemon.rb +387 -0
- data/lib/daemon/sigcomm.rb +64 -0
- data/lib/http/broker.rb +150 -0
- data/lib/http/rackup.rb +91 -0
- data/lib/http/request.rb +66 -0
- data/lib/http/response.rb +65 -0
- data/lib/plugins/dependencies.rb +285 -0
- data/lib/plugins/gui_plugin.rb +160 -0
- data/lib/plugins/guiparser.rb +123 -0
- data/lib/plugins/plugin.rb +438 -0
- data/lib/plugins/plugin_base.rb +162 -0
- data/lib/plugins/plugin_plugins.rb +81 -0
- data/lib/plugins/plugin_sqlite_db.rb +98 -0
- data/lib/plugins/pluginmanager.rb +635 -0
- data/lib/plugins/plugins.rb +169 -0
- data/lib/plugins/servlet.rb +108 -0
- data/lib/rsence.rb +32 -0
- data/lib/session/msg.rb +327 -0
- data/lib/session/sessionmanager.rb +522 -0
- data/lib/session/sessionstorage.rb +340 -0
- data/lib/transporter/transporter.rb +263 -0
- data/lib/util/gzstring.rb +9 -0
- data/lib/util/ruby19_fixes.rb +18 -0
- data/lib/values/hvalue.rb +378 -0
- data/lib/values/valuemanager.rb +172 -0
- data/plugins/client_pkg/client_pkg.rb +157 -0
- data/plugins/client_pkg/info.yaml +25 -0
- data/plugins/client_pkg/lib/client_pkg_build.rb +561 -0
- data/plugins/client_pkg/lib/client_pkg_cache.rb +50 -0
- data/plugins/client_pkg/lib/client_pkg_serve.rb +218 -0
- data/plugins/index_html/img/loading.gif +0 -0
- data/plugins/index_html/img/riassence.gif +0 -0
- data/plugins/index_html/index_html.rb +120 -0
- data/plugins/index_html/info.yaml +18 -0
- data/plugins/index_html/tmpl/index.html +15 -0
- data/plugins/main/info.yaml +18 -0
- data/plugins/main/js/main.js +84 -0
- data/plugins/main/main.rb +255 -0
- data/plugins/main/values.yaml +8 -0
- data/plugins/ticket/info.yaml +21 -0
- data/plugins/ticket/lib/common.rb +392 -0
- data/plugins/ticket/lib/favicon.rb +39 -0
- data/plugins/ticket/lib/file.rb +58 -0
- data/plugins/ticket/lib/img.rb +50 -0
- data/plugins/ticket/lib/objblob.rb +66 -0
- data/plugins/ticket/lib/rsrc.rb +34 -0
- data/plugins/ticket/lib/upload.rb +236 -0
- data/plugins/ticket/ticket.rb +333 -0
- data/setup/welcome/gui/welcome.yaml +92 -0
- data/setup/welcome/info.yaml +13 -0
- data/setup/welcome/text/welcome.html +9 -0
- data/setup/welcome/values.yaml +9 -0
- data/setup/welcome/welcome.rb +54 -0
- metadata +407 -0
@@ -0,0 +1,635 @@
|
|
1
|
+
## RSence
|
2
|
+
# Copyright 2006 Riassence Inc.
|
3
|
+
# http://riassence.com/
|
4
|
+
#
|
5
|
+
# You should have received a copy of the GNU General Public License along
|
6
|
+
# with this software package. If not, contact licensing@riassence.com
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
require 'plugins/plugins'
|
11
|
+
require 'plugins/dependencies'
|
12
|
+
|
13
|
+
module RSence
|
14
|
+
|
15
|
+
## PluginManager is the service that loads and provides method delegation
|
16
|
+
## amongst its plugin bundles.
|
17
|
+
##
|
18
|
+
## = Usage
|
19
|
+
## plugin_paths = [ 'plugins', '/home/me/rsence/plugins' ]
|
20
|
+
## myPluginManager = RSence::PluginManager.new( plugin_paths )
|
21
|
+
##
|
22
|
+
class PluginManager
|
23
|
+
|
24
|
+
attr_reader :transporter, :sessions
|
25
|
+
|
26
|
+
# Returns the registry data for plugin bundle +plugin_name+
|
27
|
+
def registry( plugin_name )
|
28
|
+
return @registry[ plugin_name ]
|
29
|
+
end
|
30
|
+
alias [] registry
|
31
|
+
|
32
|
+
# By default, calling a method not defined calls a plugin of that name
|
33
|
+
def method_missing( sym, *args, &block )
|
34
|
+
if @registry.has_key?(sym)
|
35
|
+
if args == [] and block == nil
|
36
|
+
return @registry[sym]
|
37
|
+
elsif block == nil
|
38
|
+
call( sym, *args )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Registers alias name for a plugin bundle.
|
44
|
+
def register_alias( bundle_name, alias_name )
|
45
|
+
if @aliases.has_key?( alias_name.to_sym )
|
46
|
+
warn "Alias already taken: #{alias_name.inspect}"
|
47
|
+
else
|
48
|
+
@aliases[ alias_name ] = bundle_name.to_sym
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Registers plugin class +inst+ into the registry using +bundle_name+
|
53
|
+
def register_bundle( inst, bundle_name )
|
54
|
+
bundle_name = bundle_name.to_sym
|
55
|
+
if @registry.has_key?( bundle_name )
|
56
|
+
if registry[ bundle_name ] != inst
|
57
|
+
warn "Tried to register a conflicting bundle name: #{bundle_name.inspect}; ignoring"
|
58
|
+
else
|
59
|
+
warn "Use @plugins.register_alias to register more than one name per plugin."
|
60
|
+
register_alias( inst.name.to_sym, bundle_name )
|
61
|
+
end
|
62
|
+
else
|
63
|
+
inst.init if inst.respond_to? :init and not inst.inited
|
64
|
+
@registry[ bundle_name ] = inst
|
65
|
+
if inst.respond_to?( :match ) and ( inst.respond_to?( :get ) or inst.respond_to?( :post ) )
|
66
|
+
@servlets.push( bundle_name )
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def callable?( plugin_name, method_name )
|
72
|
+
return false if @deps.category?( plugin_name )
|
73
|
+
return false unless @registry.has_key?( plugin_name )
|
74
|
+
plugin = @registry[plugin_name]
|
75
|
+
return false unless plugin.respond_to?( method_name )
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
|
79
|
+
# Calls the method +method_name+ with args +args+ of the plugin +plugin_name+.
|
80
|
+
# Returns false, if no such plugin or method exists.
|
81
|
+
def call( plugin_name, method_name, *args )
|
82
|
+
plugin_name = plugin_name.to_sym
|
83
|
+
if callable?( plugin_name, method_name )
|
84
|
+
begin
|
85
|
+
return @registry[ plugin_name ].send( method_name, *args )
|
86
|
+
rescue => e
|
87
|
+
plugin_error(
|
88
|
+
e,
|
89
|
+
"RSence::PluginManager.call error",
|
90
|
+
"plugin_name: #{plugin_name.inspect}, method_name: #{method_name.inspect}",
|
91
|
+
plugin_name
|
92
|
+
)
|
93
|
+
end
|
94
|
+
elsif @deps.category?( plugin_name )
|
95
|
+
warn "Warning! Tried to call category: #{plugin_name.inpsect}"
|
96
|
+
elsif not @registry.has_key?( plugin_name )
|
97
|
+
warn "Warning! No such plugin: #{plugin_name.inspect}"
|
98
|
+
elsif not @registry[ plugin_name ].respond_to?( method_name )
|
99
|
+
warn "Warning! Plugin: #{plugin_name.inspect} does not respond to #{method_name.inspect}"
|
100
|
+
end
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
alias run_plugin call
|
104
|
+
|
105
|
+
# Prettier error handling.
|
106
|
+
def plugin_error( e, err_location, err_location_descr, eval_repl=false )
|
107
|
+
err_msg = [
|
108
|
+
"*"*40,
|
109
|
+
err_location,
|
110
|
+
err_location_descr,
|
111
|
+
"#{e.class.to_s}, #{e.message}",
|
112
|
+
"Backtrace:",
|
113
|
+
"\t"+e.backtrace.join("\n\t"),
|
114
|
+
"*"*40
|
115
|
+
].join("\n")+"\n"
|
116
|
+
if eval_repl
|
117
|
+
puts
|
118
|
+
puts "plugin: #{eval_repl}"
|
119
|
+
puts
|
120
|
+
err_msg = err_msg.gsub(/^\t\(eval\)\:/s,"\t#{eval_repl}:")
|
121
|
+
end
|
122
|
+
$stderr.write( err_msg )
|
123
|
+
end
|
124
|
+
|
125
|
+
# Search servlets that match the +uri+ and +req_type+
|
126
|
+
def match_servlet_uri( uri, req_type=:get )
|
127
|
+
match_score = {}
|
128
|
+
@servlets.each do | servlet_name |
|
129
|
+
servlet = @registry[ servlet_name ]
|
130
|
+
next unless servlet.respond_to?( req_type )
|
131
|
+
begin
|
132
|
+
if servlet.match( uri, req_type )
|
133
|
+
score = servlet.score
|
134
|
+
match_score[ score ] = [] unless match_score.has_key? score
|
135
|
+
match_score[ score ].push( servlet_name )
|
136
|
+
end
|
137
|
+
rescue => e
|
138
|
+
plugin_error(
|
139
|
+
e,
|
140
|
+
"RSence::PluginManager.match_servlet_uri",
|
141
|
+
"servlet: #{servlet_name.inspect}, req_type: #{req_type.inspect}, uri: #{uri.inspect}",
|
142
|
+
servlet_name
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
match_scores = match_score.keys.sort
|
147
|
+
if match_scores.empty?
|
148
|
+
return false
|
149
|
+
else
|
150
|
+
matches_order = []
|
151
|
+
matches_best = match_score[ match_scores[0] ]
|
152
|
+
if matches_best.size > 1
|
153
|
+
matches_best = matches_best[ rand( matches_best.size ) ]
|
154
|
+
else
|
155
|
+
matches_best = matches_best.first
|
156
|
+
end
|
157
|
+
matches_order.push( matches_best )
|
158
|
+
match_score.keys.sort.each do |match_n|
|
159
|
+
match_score[ match_n ].each do | match_name |
|
160
|
+
matches_order.push( match_name ) unless matches_order.include? match_name
|
161
|
+
end
|
162
|
+
end
|
163
|
+
return matches_order
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Calls the servlet that matches the +req_type+ and +req.fullpath+ with
|
168
|
+
# the highest score.
|
169
|
+
def match_servlet( req_type, req, resp, session )
|
170
|
+
req_uri = req.fullpath
|
171
|
+
matches_order = match_servlet_uri( req_uri, req_type )
|
172
|
+
return false unless matches_order
|
173
|
+
matches_order.each do |servlet_name|
|
174
|
+
begin
|
175
|
+
@registry[servlet_name].send( req_type, req, resp, session )
|
176
|
+
return true
|
177
|
+
rescue => e
|
178
|
+
plugin_error(
|
179
|
+
e,
|
180
|
+
"RSence::PluginManager.match_servlet",
|
181
|
+
"servlet_name: #{servlet_name.inspect}, req_type: #{req_type.inspect}",
|
182
|
+
servlet_name
|
183
|
+
)
|
184
|
+
next
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return false
|
188
|
+
end
|
189
|
+
|
190
|
+
# Delegates +method_name+ with +args+ to any loaded
|
191
|
+
# plugin that responds to the method.
|
192
|
+
def delegate( method_name, *args )
|
193
|
+
@deps.list.each do |plugin_name|
|
194
|
+
call( plugin_name, method_name, *args ) if callable?( plugin_name, method_name )
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Reverse delegate +method_name+ with +args+ to any loaded
|
199
|
+
# plugin that responds to the method.
|
200
|
+
def delegate_reverse( method_name, *args )
|
201
|
+
@deps.list.reverse.each do |plugin_name|
|
202
|
+
call( plugin_name, method_name, *args ) if callable?( plugin_name, method_name )
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Delegates the +flush+ and +close+ methods to any
|
207
|
+
# loaded plugins, in that order.
|
208
|
+
def shutdown
|
209
|
+
@transporter.online = false
|
210
|
+
@deps.list.reverse.each do |bundle_name|
|
211
|
+
unload_bundle( bundle_name )
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Finds the most recent file in the path
|
216
|
+
def most_recent( bundle_path, newest_date=0 )
|
217
|
+
path_date = File.stat( bundle_path ).mtime.to_i
|
218
|
+
is_dir = File.directory?( bundle_path )
|
219
|
+
if path_date > newest_date and not is_dir
|
220
|
+
newest_date = path_date
|
221
|
+
end
|
222
|
+
if is_dir
|
223
|
+
Dir.entries( bundle_path ).each do |entry_name|
|
224
|
+
next if entry_name[0].chr == '.'
|
225
|
+
full_path = File.join( bundle_path, entry_name )
|
226
|
+
unless File.directory?( full_path )
|
227
|
+
has_dot = entry_name.include?('.')
|
228
|
+
next unless has_dot
|
229
|
+
is_src_file = ['yaml','rb'].include?( entry_name.split('.')[-1] )
|
230
|
+
next unless is_src_file
|
231
|
+
end
|
232
|
+
newest_date = most_recent( full_path, newest_date )
|
233
|
+
end
|
234
|
+
end
|
235
|
+
return newest_date
|
236
|
+
end
|
237
|
+
|
238
|
+
# Gets plugin information
|
239
|
+
def bundle_info( bundle_path, bundle_name, src_file )
|
240
|
+
|
241
|
+
# Default bundle information
|
242
|
+
info = {
|
243
|
+
|
244
|
+
# The human-readable product name of the package
|
245
|
+
:title => bundle_name.to_s.capitalize,
|
246
|
+
|
247
|
+
# The human-readable version of the package
|
248
|
+
:version => '0.0.0',
|
249
|
+
|
250
|
+
# A brief description of the package (rdoc formatting supported)
|
251
|
+
:description => 'No Description',
|
252
|
+
|
253
|
+
# A flag (when false) prevents the plugin from automatically reload when changed.
|
254
|
+
:reloadable => true,
|
255
|
+
|
256
|
+
# System version requirement.
|
257
|
+
# NOTE: Has no effect yet!
|
258
|
+
:sys_version => '>= 1.0.0',
|
259
|
+
|
260
|
+
# Dependency, by default the system category (built-in plugins).
|
261
|
+
# A nil ( "~" in yaml ) value means no dependencies.
|
262
|
+
:depends_on => [ :system ],
|
263
|
+
|
264
|
+
# Optional, name of category. The built-in plugins are :system
|
265
|
+
:category => nil,
|
266
|
+
|
267
|
+
# Optional, name of plugin to replace
|
268
|
+
# NOTE: Has no effect yet!
|
269
|
+
:replaces => nil,
|
270
|
+
|
271
|
+
# Optional, reverse dependency. Loads before the prepended plugin(category).
|
272
|
+
# NOTE: Doesn't support packages yet!
|
273
|
+
:prepends => nil
|
274
|
+
|
275
|
+
}
|
276
|
+
|
277
|
+
# Merge info.yaml data into info
|
278
|
+
info_path = File.join( bundle_path, 'info.yaml' )
|
279
|
+
if File.exists?( info_path )
|
280
|
+
info_yaml = YAML.load( File.read( info_path ) )
|
281
|
+
info_yaml.each do |info_key,info_value|
|
282
|
+
info[ info_key.to_sym ] = info_value
|
283
|
+
end
|
284
|
+
else
|
285
|
+
warn "Expected info.yaml, using defaults:"
|
286
|
+
warn " #{info_path}"
|
287
|
+
end
|
288
|
+
|
289
|
+
@deps.set_deps( bundle_name, info[:depends_on] )
|
290
|
+
if info[:category]
|
291
|
+
if info[:category].class == Symbol
|
292
|
+
@deps.add_category( info[:category] ) unless @deps.category?( info[:category] )
|
293
|
+
@deps.set_deps( info[:category], bundle_name )
|
294
|
+
else
|
295
|
+
warn "Invalid category: #{info[:category].inspect}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
if info[:prepends]
|
299
|
+
if info[:prepends].class == Array
|
300
|
+
info[:prepends].each do |prep|
|
301
|
+
@deps.set_deps( prep, bundle_name )
|
302
|
+
end
|
303
|
+
else
|
304
|
+
@deps.set_deps( info[:prepends], bundle_name )
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Extra information, not override-able in info.yaml
|
309
|
+
|
310
|
+
# Path of bundle
|
311
|
+
info[:path] = bundle_path
|
312
|
+
|
313
|
+
# Name of bundle
|
314
|
+
info[:name] = bundle_name
|
315
|
+
|
316
|
+
# Full path of source file
|
317
|
+
info[:src_file] = src_file
|
318
|
+
|
319
|
+
# Timestamp of last changed file
|
320
|
+
info[:last_changed] = most_recent( bundle_path )
|
321
|
+
|
322
|
+
# ..however, don't accept future timestamps:
|
323
|
+
time_now = Time.now.to_i
|
324
|
+
info[:last_changed] = time_now if info[:last_changed] > time_now
|
325
|
+
|
326
|
+
return info
|
327
|
+
end
|
328
|
+
|
329
|
+
# Loads a plugin bundle.
|
330
|
+
def load_bundle( name )
|
331
|
+
|
332
|
+
if @deps.unresolved?(name)
|
333
|
+
warn "Warning: Bundle #{name} has unmet dependencies."
|
334
|
+
return
|
335
|
+
end
|
336
|
+
|
337
|
+
if @registry.has_key?( name )
|
338
|
+
warn "Warning: Bundle #{name} already loaded."
|
339
|
+
return
|
340
|
+
end
|
341
|
+
puts "Loading bundle: #{name.inspect}" if RSence.args[:debug]
|
342
|
+
|
343
|
+
info = @info[ name ]
|
344
|
+
|
345
|
+
path = info[:path]
|
346
|
+
src_file = info[:src_file]
|
347
|
+
|
348
|
+
bundle_src = File.read( src_file )
|
349
|
+
|
350
|
+
module_ns = Plugins.bundle_loader( {
|
351
|
+
:bundle_path => path,
|
352
|
+
:bundle_name => name,
|
353
|
+
:bundle_info => info,
|
354
|
+
:plugin_manager => self,
|
355
|
+
:src_path => src_file,
|
356
|
+
:src => bundle_src
|
357
|
+
} )
|
358
|
+
|
359
|
+
module_ns.constants.each do |module_const_name|
|
360
|
+
module_const = module_ns.const_get( module_const_name )
|
361
|
+
if module_const.class == Class
|
362
|
+
type = module_const.bundle_type
|
363
|
+
if [:Servlet, :Plugin, :GUIPlugin].include? type
|
364
|
+
bundle_inst = module_const.new( name, info, path, self )
|
365
|
+
bundle_inst.register( name ) if [ :Plugin, :GUIPlugin ].include?( type )
|
366
|
+
break
|
367
|
+
else
|
368
|
+
warn "Can't init class: #{module_const.to_s}"
|
369
|
+
break
|
370
|
+
end
|
371
|
+
else
|
372
|
+
warn "Invalid module_const.class: #{module_const.class.inspect}"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# loads all bundles found in order of dependency
|
378
|
+
def load_bundles
|
379
|
+
@deps.list.each do |name|
|
380
|
+
load_bundle( name ) if @deps.loadable?( name )
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# If a bundle is found, set its dependencies etc
|
385
|
+
def bundle_found( bundle_path, bundle_name, src_file )
|
386
|
+
@info[ bundle_name ] = bundle_info( bundle_path, bundle_name, src_file )
|
387
|
+
end
|
388
|
+
|
389
|
+
# Returns false, if the plugin directory isn't valid.
|
390
|
+
# Returns [bundle_path, src_file] otherwise.
|
391
|
+
def valid_plugindir?( path, bundle_name )
|
392
|
+
return false if bundle_name[0].chr == '.'
|
393
|
+
bundle_path = File.expand_path( File.join( path, bundle_name ) )
|
394
|
+
return false unless File.directory?( bundle_path )
|
395
|
+
bundle_file = bundle_name+'.rb'
|
396
|
+
src_file = File.join( bundle_path, bundle_file )
|
397
|
+
if not File.exists?( src_file )
|
398
|
+
bundle_file = 'main.rb'
|
399
|
+
src_file = File.join( bundle_path, bundle_file )
|
400
|
+
return false unless File.exists?( src_file )
|
401
|
+
end
|
402
|
+
return [ bundle_path, src_file ]
|
403
|
+
end
|
404
|
+
|
405
|
+
# Returns true, if the bundle is disabled
|
406
|
+
def disabled?( bundle_path )
|
407
|
+
File.exists?( File.join( bundle_path, 'disabled' ) )
|
408
|
+
end
|
409
|
+
|
410
|
+
# Returns true, if the bundle is loaded.
|
411
|
+
def loaded?( bundle_name )
|
412
|
+
@registry.has_key?( bundle_name )
|
413
|
+
end
|
414
|
+
|
415
|
+
# Scans a directory of plugins, calls +load_plugin+ for bundles that match
|
416
|
+
# the definition of a plugin bundle.
|
417
|
+
# - Skips bundles starting with a dot
|
418
|
+
# - Skips bundles without a ruby source file with the same
|
419
|
+
# name as the directory (plus '.rb').
|
420
|
+
# - Skips bundles containing a file or directory named 'disabled'
|
421
|
+
def find_bundles( path )
|
422
|
+
bundles_found = []
|
423
|
+
Dir.entries(path).each do |bundle_name|
|
424
|
+
bundle_status = valid_plugindir?( path, bundle_name )
|
425
|
+
if bundle_status
|
426
|
+
(bundle_path, src_file) = bundle_status
|
427
|
+
unless disabled?( bundle_path )
|
428
|
+
bundles_found.push( [bundle_path, bundle_name.to_sym, src_file] )
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
return bundles_found
|
433
|
+
end
|
434
|
+
|
435
|
+
# Unloads the plugin bundle named +bundle_name+
|
436
|
+
def unload_bundle( bundle_name )
|
437
|
+
if @registry.has_key?( bundle_name )
|
438
|
+
unload_order = @deps.del_order( bundle_name )
|
439
|
+
unload_order.each do |unload_dep|
|
440
|
+
unload_bundle( unload_dep ) unless unload_dep == bundle_name
|
441
|
+
end
|
442
|
+
puts "Unloading bundle: #{bundle_name.inspect}" if RSence.args[:debug]
|
443
|
+
@deps.del_item( bundle_name )
|
444
|
+
online_status = @transporter.online?
|
445
|
+
@transporter.online = false
|
446
|
+
call( bundle_name, :flush )
|
447
|
+
call( bundle_name, :close )
|
448
|
+
@registry.delete( bundle_name )
|
449
|
+
@aliases.each do |a_name,b_name|
|
450
|
+
if b_name == bundle_name
|
451
|
+
@aliases.delete( a_name )
|
452
|
+
end
|
453
|
+
end
|
454
|
+
if @servlets.include?( bundle_name )
|
455
|
+
@servlets.delete( bundle_name )
|
456
|
+
end
|
457
|
+
if @info.include?( bundle_name )
|
458
|
+
@info.delete( bundle_name )
|
459
|
+
end
|
460
|
+
@transporter.online = online_status
|
461
|
+
return unload_order
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# Returns true, if a plugin bundle has changed.
|
466
|
+
# Only compares timestamp, not checksum.
|
467
|
+
def changed?( plugin_name )
|
468
|
+
info = @info[plugin_name]
|
469
|
+
last_changed = info[:last_changed]
|
470
|
+
newest_change = most_recent( info[:path], last_changed )
|
471
|
+
return last_changed < newest_change
|
472
|
+
end
|
473
|
+
|
474
|
+
# Logs and speaks the message, if the speech synthesis command "say" exists.
|
475
|
+
def say( message )
|
476
|
+
puts message
|
477
|
+
if RSence.args[:say]
|
478
|
+
Thread.new do
|
479
|
+
Thread.pass
|
480
|
+
system(%{say "#{message.gsub('"','')}"})
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# Checks for changed plugin bundles and unloads/loads/reloads them accordingly.
|
486
|
+
def update_bundles!
|
487
|
+
(are_found, to_load, to_unload, to_reload) = [[],[],[],[]]
|
488
|
+
found_map = {}
|
489
|
+
@plugin_paths.each do |path|
|
490
|
+
are_found += find_bundles( path )
|
491
|
+
end
|
492
|
+
are_found.each do |item|
|
493
|
+
(path, name, src_file) = item
|
494
|
+
found_map[name] = item
|
495
|
+
is_loaded = loaded?( name )
|
496
|
+
if is_loaded and changed?( name )
|
497
|
+
to_reload.push( name )
|
498
|
+
elsif not is_loaded
|
499
|
+
to_load.push( name )
|
500
|
+
end
|
501
|
+
end
|
502
|
+
@registry.keys.each do |name|
|
503
|
+
to_unload.push( name ) if not found_map.has_key?( name )
|
504
|
+
end
|
505
|
+
to_unload.each do |name|
|
506
|
+
next if @deps.category?( name )
|
507
|
+
if RSence.args[:verbose]
|
508
|
+
print "Unloading #{name.inspect}..."; STDOUT.flush
|
509
|
+
end
|
510
|
+
unload_bundle( name )
|
511
|
+
puts "done!" if RSence.args[:verbose]
|
512
|
+
end
|
513
|
+
to_reload.each do |name|
|
514
|
+
next if @deps.category?( name )
|
515
|
+
if RSence.args[:verbose]
|
516
|
+
print "Unloading #{name.inspect}..."; STDOUT.flush
|
517
|
+
end
|
518
|
+
unload_order = unload_bundle( name )
|
519
|
+
to_load += unload_order
|
520
|
+
puts "done!" if RSence.args[:verbose]
|
521
|
+
end
|
522
|
+
info_map = {}
|
523
|
+
to_load.each do |name|
|
524
|
+
next unless found_map.has_key? name
|
525
|
+
info_map[name] = bundle_info( *found_map[name] )
|
526
|
+
end
|
527
|
+
no_deps = {}
|
528
|
+
to_load.dup.each do |name|
|
529
|
+
if @deps.unresolved?( name )
|
530
|
+
no_deps[ name ] = @deps.deps_on( name )
|
531
|
+
@deps.del_item( name )
|
532
|
+
to_load.delete( name )
|
533
|
+
end
|
534
|
+
end
|
535
|
+
to_open = []
|
536
|
+
@deps.list.each do |name|
|
537
|
+
next if @deps.category?( name )
|
538
|
+
next unless to_load.include?( name )
|
539
|
+
info = info_map[name]
|
540
|
+
if RSence.args[:verbose]
|
541
|
+
if to_reload.include?( name )
|
542
|
+
print "Reloading #{name.inspect}..."
|
543
|
+
else
|
544
|
+
print "Loading #{name.inspect}..."
|
545
|
+
end
|
546
|
+
STDOUT.flush
|
547
|
+
end
|
548
|
+
@info[name] = info
|
549
|
+
load_bundle( name )
|
550
|
+
to_open.push( name )
|
551
|
+
puts "done!" if RSence.args[:verbose]
|
552
|
+
end
|
553
|
+
unless no_deps.empty?
|
554
|
+
warn "Warning! Unable to load the following bundles; missing dependencies:"
|
555
|
+
no_deps.each do |name,deps|
|
556
|
+
warn " #{name} depends on: #{deps.join(', ')}"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
to_open.each do |name|
|
560
|
+
if RSence.args[:verbose]
|
561
|
+
print "Opening #{name.inspect}..."; STDOUT.flush
|
562
|
+
end
|
563
|
+
call( name, :open )
|
564
|
+
puts "done!" if RSence.args[:verbose]
|
565
|
+
end
|
566
|
+
if not (to_load.empty? and to_unload.empty? and to_reload.empty?)
|
567
|
+
puts "Plugin bundles:"
|
568
|
+
puts " loaded: #{to_load.join(', ')}" unless to_load.empty?
|
569
|
+
puts " unloaded: #{to_unload.join(', ')}" unless to_unload.empty?
|
570
|
+
puts " reloaded: #{to_reload.join(', ')}" unless to_reload.empty?
|
571
|
+
puts " opened: #{to_open.join(', ')}" unless to_open.empty?
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
# Top-level method for scanning all plugin directories.
|
576
|
+
# Clears previously loaded plugins.
|
577
|
+
def init_bundles!
|
578
|
+
@registry = {} # bundle_name => bundle_instance mapping
|
579
|
+
@info = {} # bundle_name => bundle_info mapping
|
580
|
+
@aliases = {} # bundle_alias => bundle_name mapping
|
581
|
+
@servlets = [] # bundle_name list of Servlet class instances
|
582
|
+
update_bundles!
|
583
|
+
end
|
584
|
+
|
585
|
+
# Initialize with a list of directories as plugin_paths.
|
586
|
+
# It's an array containing all plugin directories to scan.
|
587
|
+
def initialize( plugin_paths, transporter=nil,
|
588
|
+
autoreload=false, name_prefix=false,
|
589
|
+
resolved_deps=[], resolved_categories={} )
|
590
|
+
if transporter
|
591
|
+
@transporter = transporter
|
592
|
+
@sessions = transporter.sessions
|
593
|
+
end
|
594
|
+
@name_prefix = name_prefix
|
595
|
+
@plugin_paths = plugin_paths
|
596
|
+
@deps = Dependencies.new( resolved_deps, resolved_categories )
|
597
|
+
puts "Loading #{name_prefix+' ' if name_prefix}plugins..." if RSence.args[:verbose]
|
598
|
+
init_bundles!
|
599
|
+
puts %{Plugins #{"of #{name_prefix} " if name_prefix}loaded.} if RSence.args[:verbose]
|
600
|
+
if autoreload
|
601
|
+
@thr = Thread.new do
|
602
|
+
Thread.pass
|
603
|
+
while true
|
604
|
+
begin
|
605
|
+
update_bundles!
|
606
|
+
rescue => e
|
607
|
+
plugin_error( e, "PluginManager#update_bundles!", "An error occurred while reloading bundles" )
|
608
|
+
end
|
609
|
+
sleep 3
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
|
618
|
+
|
619
|
+
|
620
|
+
|
621
|
+
|
622
|
+
|
623
|
+
|
624
|
+
|
625
|
+
|
626
|
+
|
627
|
+
|
628
|
+
|
629
|
+
|
630
|
+
|
631
|
+
|
632
|
+
|
633
|
+
|
634
|
+
|
635
|
+
|