lib/rsence/plugins/plugin.rb in rsence-pre-3.0.0.8 vs lib/rsence/plugins/plugin.rb in rsence-pre-3.0.0.9
- old
+ new
@@ -1,26 +1,26 @@
module RSence
module Plugins
-
+
# The Plugin__ is actually available as +Plugin+ from plugin bundles using the {RSence::Plugins::Plugin} class mimic method.
- #
+ #
# The Plugin class is the base class for extending server logic. A single Plugin instance serves the requests of all sessions, which makes them very cpu and memory efficient compared to systems, where the server classes are constructed and destructed for each request.
- #
+ #
# Plugins are designed to be contained in a plugin directory bundle and to be loaded by the main {PluginManager}, which is also responsible for delegating the events and other calls throughout the system.
#
- #
+ #
# == Anatomy of a plugin bundle
#
# First read about the {file:PluginBundles Plugin Bundles}.
#
# The plugin bundle contains all data needed to run the plugin. Design your plugin without any hard-coded paths, remember that it's intended to be installed by moving or copying the whole plugin into one of the "plugins" directories. Use {#bundle_path} to construct paths.
- #
+ #
# The {PluginManager} looks for these bundles and evaluates them into an anonymous +Module+ namespace. The contents of the ruby source file is then responsible for including its libraries, constructing an instance of itself and registering itself as a part of the system.
- #
+ #
# Use the {GUIPlugin__ GUIPlugin} class for plugins that handle user interfaces. Use the {Plugin__ Plugin} class for plugin bundles that provide supplemental functionality, value responders and other utilities that supplement the user interface.
- #
+ #
# = Extension hooks for server events
# These methods are provided as the basic server event hooks:
# * {#init +#init+} -- Use instead of +initialize+
# * {#open +#open+} -- Extend to open objects
# * {#flush +#flush+} -- Extend to write the state and to flush buffers
@@ -28,16 +28,16 @@
#
# = Extension hooks for session events
# * {#idle +#idle+} -- Extend to implement logic for each client data synchronization and "idle poll" request.
# * {#init_ses +#init_ses+} -- Extend to implement logic when a new session is created. A new session is created, when a user enters accesses the server the first time, or the first time after the previous session expired.
# * {#restore_ses +#restore_ses+} -- Extend to implement logic when an old session is restored. A session is restored, when the user returns to the page or reloads the page before the session is expired.
- #
+ #
# == Extension hooks for session events, If the server is configured to restore and clone previous sessions (default):
# When sessions are cloned, the previous session is not invalidated and exists until timing out as a result of the web browser window being closed or client computer losing network connectivity for a certain (configurable) time frame.
# * {#cloned_target +#cloned_target+} -- Extend to implement logic in the request when session is a clone of the original session.
# * {#cloned_source +#cloned_source+} -- Extend to implement logic in the next request of the original session after it has been cloned.
- #
+ #
# == If the server is configured to not clone sessions:
# When the user accesses the same page using the same browser (in different tabs or windows), only the most recently restored one is valid, while the previous ones are immediately invalidated. This is a more secure mode of operation, but has several drawback to usability, so it's not enabled by default.
#
# = Utility methods
# These are general utility methods not intended to be extended.
@@ -61,103 +61,103 @@
# * {file:PluginBundleInfo Plugin Bundle +info.yaml+ files} -- General information about the meta-information files.
# * {PluginBase PluginBase} -- The PluginBase module
# * {Servlet__ Servlet} -- The Servlet base class
# * {GUIPlugin__ GUIPlugin} -- The GUIPlugin base class
class Plugin__
-
-
+
+
include PluginBase
-
+
# @private Class type identifier for the PluginManager.
# @return [:Plugin]
def self.bundle_type; :Plugin; end
-
+
# @return [Symbol] The name of the plugin bundle
attr_reader :name
-
+
# @return [String] The absolute path of the plugin bundle.
attr_reader :path
-
+
# @return [Hash] The {file:PluginBundleInfo meta-information} of the plugin bundle.
attr_reader :info
-
+
# @private State of the plugin.
attr_reader :inited
-
+
# @private The constructor should not be accessed from anywhere else than the PluginManager, which does it automatically.
def initialize( bundle_name, bundle_info, bundle_path, plugin_manager )
@inited = false
@info = bundle_info
@name = bundle_name
@path = bundle_path
@plugins = plugin_manager
register unless @info[:inits_self]
end
-
+
# Extend this method to do any initial tasks before other methods are called.
#
# By default {#init_values} is called to load the {file:Values +values.yaml+} configuration file.
#
# @return [nil]
#
# @see PluginBase#init
def init
@values = init_values
end
-
+
# Extend this method to do any tasks every time the client makes a request.
#
# @param [Message] msg The message is supplied by the system.
#
# @return [nil]
def idle( msg )
end
-
+
# Extend this method to invoke actions, when a new session is created.
#
# By default {#init_ses_values} is called to initialize values defined in the {file:Values +values.yaml+} configuration file.
#
# @param [Message] msg The message is supplied by the system.
#
# @return [nil]
def init_ses( msg )
init_ses_values( msg )
end
-
+
# Extend this method to invoke actions, when a previous session is restored.
#
# By default +#restore_ses_values+ is called to perform actions on values as defined in the {file:Values +values.yaml+} configuration file.
#
# @param [Message] msg The message is supplied by the system.
#
# @return [nil]
def restore_ses( msg )
restore_ses_values( msg )
end
-
+
# Extend this method to invoke actions, when the session is a clone of another session. It's called once, just before {#restore_ses} is called.
#
# A session is cloned, when a user opens a another browser window or tab, while the previous session is still active.
#
# @param [Message] msg The message is supplied by the system.
# @param [Hash] source_session The actual previous session object, which was used as the source of the clone.
#
# @return [nil]
def cloned_target( msg, source_session )
end
-
+
# Extend this method to invoke actions, when the session has been cloned to another session. It's called once, just before {#restore_ses} is called on the first request after the cloning happened.
#
# A session is cloned, when a user opens a another browser window or tab, while the previous session is still active.
#
# @param [Message] msg The message is supplied by the system.
# @param [Hash] target_session The actual cloned session object, which is a copy of the current session.
#
# @return [nil]
def cloned_source( msg, target_sessions )
end
-
+
# @private This method must be called to register the plugin instance into the system. Otherwise, it's subject to destruction and garbage collection. Use the +name+ parameter to give the (unique) name of your plugin.
def register( name=false )
if @inited
@plugins.register_alias( @name, name )
else
@@ -168,19 +168,19 @@
end
@plugins.register_bundle( self, name )
@inited = true
end
end
-
+
def name_with_manager_s
if @info[:manager]
return "#{@info[:manager].to_s}:#{@name.to_s}"
else
return @name.to_s
end
end
-
+
# This method returns (or creates and returns) the entry in the session based on the name your plugin is registered as. It's advised to use this call instead of manually managing {Message#session msg#session} in most cases.
#
# Uses the first name registered for the plugin and converts it to a symbol.
#
# @param [Message] msg The message is supplied by the system.
@@ -253,11 +253,11 @@
end
def squeezed_js( path )
client_pkg.squeeze( file_read( path ) )
end
-
+
# Returns the source code of the javascript file +js_name+ in the 'js' subdirectory of the plugin bundle. Use it to send raw javascript command code to the client. Use {#read_js_once} for libraries.
#
# @param [String] js_name Javascript source file name without the '.js' suffix.
#
# @return [String] The source of the file.
@@ -271,11 +271,11 @@
return brew_coffee( path ) if type == :coffee
# return squeezed_js( path ) if type == :js
return file_read( path ) if type == :js
warn "read_js: unknown type #{type.inspect}"
end
-
+
# Like {#read_js}, but reads the file only once per session. Use for inclusion of custom library code.
#
# @param [Message] msg The message is supplied by the system.
# @param [String] js_name Javascript source file name without the '.js' suffix.
#
@@ -293,18 +293,18 @@
return read_js( path )
else
return ''
end
end
-
+
# Extracts +HValue+ references as javascript from the session Hash.
#
# @param [Message] msg The message is supplied by the system.
# @param [Hash] ses Used for supplying a Hash with the {HValue} instances. It's optional and defaults to the current plugin node in the active session.
- #
+ #
# @return [String] A string representing a javascript object similar to the ruby Hash +ses+.
- #
+ #
# @example
# values_js( msg, get_ses(msg) )
def values_js( msg, ses=false )
# backwards-compatible with pre-1.3 behavior
ses = msg if msg.class == Hash
@@ -316,12 +316,12 @@
js_references.push( "#{key_name.to_s}:HVM.values['#{ses[key_name].val_id}']" )
end
end
return "{#{js_references.join(',')}}"
end
-
-
+
+
# Tells the js client framework to load a list of pre-packaged client libraries.
#
# It keeps track of what's loaded, so nothing library loaded twice.
#
# @param [Message] msg The message is supplied by the system.
@@ -352,11 +352,11 @@
ses[:deps].push( dependency )
msg.reply(%{jsLoader.load("#{dependency}");})
end
end
end
-
+
# @private Returns a hash with valid symbol keys for +#value_call+.
def sanitize_value_call_hash( hash_dirty )
if hash_dirty.class == Symbol
return { :method => hash_dirty }
elsif hash_dirty.class == String
@@ -392,11 +392,11 @@
warn "Undefined value_call key: #{key.inspect}"
end
end
return hash_clean
end
-
+
# @private Returns a sanitized copy of a single responder specification.
def sanitize_value_responders( responders_dirty )
if responders_dirty.class != Array
responders_dirty = [ responders_dirty ]
end
@@ -435,20 +435,22 @@
warn "Responder (#{responder_clean.inspect}) is missing a :method specification. Skipping.."
end
end
return responders_clean
end
-
+
# @private Returns a sanitized copy of a single value item.
def sanitize_value_item( value_item_dirty )
unless value_item_dirty.class == Hash
value_item_dirty = {
:value => value_item_dirty,
:restore_default => false
}
end
- value_item_clean = {}
+ value_item_clean = {
+ :type => 0
+ }
value_item_dirty.each do | key, value |
if key.to_sym == :value or key.to_sym == :data
if [Array, Hash, String, TrueClass, FalseClass, Fixnum, Bignum, Float, NilClass].include? value.class
value_item_clean[:value] = value
else
@@ -468,17 +470,31 @@
value_item_clean[:restore_default] = true
end
elsif key.to_sym == :responders or key.to_sym == :responder
sanitized_responders = sanitize_value_responders( value )
value_item_clean[:responders] = sanitized_responders unless sanitized_responders.empty?
+ elsif key.to_sym == :type
+ if [0,1,2].include? value
+ value_type = value
+ elsif ['normal',:normal,'push',:push,'pull',:pull].include? value
+ # server concept of push and pull are reversed relative to the client
+ value_type = {
+ 'normal' => 0, :normal => 0,
+ 'pull' => 1, :pull => 1,
+ 'push' => 2, :push => 2
+ }[value]
+ else
+ warn "Unsupported value type: #{value}"
+ end
+ value_item_clean[:type] = value_type
else
warn "Unsupported value specification key: #{key.inspect}."
end
end
return value_item_clean
end
-
+
# @private Returns sanitized hash of the structure specified in values.yaml
def sanitize_values_yaml( values_path )
values_dirty = yaml_read( values_path )
return false if values_dirty == false
if values_dirty.class == Hash
@@ -500,19 +516,19 @@
else
warn "Unsupported format of #{bundle_path('values.yaml')}, got: #{values_dirty.inspect}, expected Hash or Array."
end
return false
end
-
+
# @private This method looks looks for a file called "values.yaml" in the plugin's bundle directory.
# If this file is found, it loads it for initial value definitions.
# These definitions are accessible as the +@values+ attribute.
def init_values
values_path = bundle_path( 'values.yaml' )
return sanitize_values_yaml( values_path )
end
-
+
# @private Creates a new instance of HValue, assigns it as +value_name+ into the
# session and uses the +value_properties+ Hash to define the default
# value and value responders.
#
# This method is invoked automatically, when handling the properties
@@ -523,22 +539,22 @@
# Structure of +value_properties+, all top-level items are optional:
#
# {
# # Default value; defaults to 0
# :value => 'foo',
- #
+ #
# # A plugin method to call to define the default value instead of the one defined in :value
# :value_call => {
# :plugin => 'plugin_name', # defaults to the plugin where defined
# :method => 'method_name', # mandatory; name of the method to call
# :args => [ 1, 'foo', 3 ], # optional, list of parameter values for the :method
# :uses_msg => true # defaults to true; when false, doesn't pass the msg as the first parameter
# },
- #
+ #
# # Restore the default, when the session is restored; defaults to true
# :restore_default => false,
- #
+ #
# # List of value responder methods to bind.
# :responders => [
# {
# :plugin => 'plugin_name', # defaults to the plugin where defined
# :method => 'method_name' # mandatory, name of the method to call
@@ -556,16 +572,16 @@
default_value = value_properties[:value]
else
default_value = 0
end
name = name_with_manager_s
- ses[value_name] = HValue.new( msg, default_value, { :name => "#{name}.#{value_name}" } )
+ ses[value_name] = HValue.new( msg, default_value, { :name => "#{name}.#{value_name}", :type => value_properties[:type] } )
if value_properties.has_key?(:responders)
init_responders( msg, ses[value_name], value_properties[:responders] )
end
end
-
+
# @private Initialize a responder for a value.
def init_responder( msg, value, responder )
name = name_with_manager_s
if responder.has_key?(:plugin)
responder_plugin = responder[:plugin]
@@ -577,11 +593,11 @@
if not value.bound?( responder_plugin, responder_method )
value.bind( responder_plugin, responder[:method] )
end
end
end
-
+
# @private Initialize several responders for a value
def init_responders( msg, value, responders )
members = value.members
release_list = []
members.each_key do |pre_plugin|
@@ -612,22 +628,22 @@
end
responders.each do |responder|
init_responder( msg, value, responder )
end
end
-
+
# @private Releases all responders of a value
def release_responders( msg, value )
members = value.members
members.each_key do |responder_plugin|
members.each do |responder_method|
value.release( responder_plugin, responder_method )
end
end
end
-
- # @private Initializes session values, if the contents of the +values.yaml+
+
+ # @private Initializes session values, if the contents of the +values.yaml+
# file is defined in the bundle directory and loaded in +#init_values+.
def init_ses_values( msg )
return unless @values
if @values.class == Array
@values.each do | value_item |
@@ -639,11 +655,11 @@
@values.each do | value_name, value_properties |
init_ses_value( msg, value_name, value_properties )
end
end
end
-
+
# @private Returns a value based on the :method and :plugin members of the
# +value_call+ hash.
#
# The call is made via msg.run if the method is not defined in
# the local plugin bundle.
@@ -691,11 +707,11 @@
return self.method( value_call_method ).call( )
end
end
end
end
-
+
def restore_ses_value( msg, value_name, value_properties )
ses = get_ses( msg )
if ses.has_key?( value_name ) and ses[ value_name ].class == HValue
if value_properties.has_key?(:responders)
init_responders( msg, ses[value_name], value_properties[:responders] )
@@ -714,10 +730,10 @@
end
else
init_ses_value( msg, value_name, value_properties )
end
end
-
+
# @private Restores session values to default, unless specified otherwise.
#
# Called from +#restore_ses+
def restore_ses_values( msg )
return unless @values