lib/values/hvalue.rb in rsence-2.0.0.10.pre vs lib/values/hvalue.rb in rsence-2.0.0.11

- old
+ new

@@ -5,199 +5,249 @@ # You should have received a copy of the GNU General Public License along # with this software package. If not, contact licensing@riassence.com ## + module RSence - ## HValue is the server-side representation of the client's HValue object. - ## It's the 'messenger' to syncronize server-client data and is smart enough - ## to validate and process itself as well as tell the client-side - ## representation of itself. + # HValue is the model for client-server synchronized values. + # A value contains its payload {#data} and enough meta-data to define its behavior. class HValue - - attr_reader :valid, :sync, :val_id, :data, :members - attr_writer :valid, :val_id - - # Method for binding the value to the session data. - def add( msg ) + # The validity of the data. Defaults to false, when the data comes from the client. + # @return [Boolean] True, when set by the server. False when initially set by the client. Also false, unless all responders return true. + attr_reader :is_valid + + # @private Is true when changed by the server. Causes the ValueManager to send its client-side representation. + attr_reader :sync + + # @private The unique ID of the value. + attr_reader :value_id + alias val_id value_id + + # The payload data. Use {#set} to change. + attr_reader :data + + # @private List of responders + attr_reader :members + + # @private + attr_writer :is_valid + + # @private + def value_id=(new_id) + @value_id = new_id + end + # @private + alias val_id= value_id= + + alias valid is_valid + alias valid? is_valid + + # @private Method for binding the value to the session data. + def add( msg ) + # get the value storage from the session data session_values = msg.session[:values][:by_id] - + ## Store the object here - session_values[ @val_id ] = self - + session_values[ @value_id ] = self + ## Sends the client-side description restore( msg ) - + ## Set the valid flag, so we know that the value is initially in sync - @valid = true + @is_valid = true end - - ## (Re-)Send the client-size representation - def restore( msg ) + # @private (Re-)Send the client-size representation + def restore( msg ) + ## Tags itself as a new value from the client's point of view @is_new_to_client = true - + add_to_sync( msg ) - + end - - # +HValue+ constructor. Binds HValue automatically to the +Message+ instance - # given as parameter. Data given as second parameter. - def initialize( msg, data, meta = { :name => nil } ) + # Value meta-data. The third constructor parameter + attr_accessor :meta + + # Creates a new client-server automatically synchronized data wrapper object. + # @param [Message] msg Just pass on the +msg+ from the scope you call from. + # @param [#to_json] data Any data that can be converted to JSON. + # @param [Hash] meta Has no effect yet. + def initialize( msg, data, meta = { :name => nil } ) + ## Get an unique integer id for the value if RSence.args[:debug] and meta[:name] and not msg.valuemanager.id_exists?( msg, meta[:name] ) - @val_id = meta[:name] + @value_id = meta[:name] else - @val_id = msg.valuemanager.randgen.gen + @value_id = msg.valuemanager.randgen.gen end - + + @meta = meta + ## set the data of the hvalue set( msg, data, true ) - + ## the @sync flag is raised, when the client data is older than the server data @sync = false - - ## the @is_valid flas is lowered, when the client data is newer than the server data + + ## the @is_valid flag is lowered, when the client data is newer than the server data @is_valid = true - + ## Bind the value to the value manager and report it to the client add( msg ) - + ## storage for validator bindings @members = {} - + end - - # Binds the value to the plugin method (both as - # strings; plugin as the name registered in PluginManager) + + # Binds the value to a responder. The responder is an instance of {Plugins::Plugin__ Plugin} or {Plugins::GUIPlugin__ GUIPlugin}. + # Responders are called once, when the client requests the data to be changed. + # Responders must return +true+, if they accept the change. Otherwise the data is treated as invalid. + # Responders must respond to exactly two parameters: ( (Message) +msg+, (HValue) +value+ ) # - # It uses strings instead of '...method(...)' because - # it won't work with marshal. Strings are easier and work - # as well. + # @param [Symbol] plugin_name The name of the registered plugin to call with the +method_name+ + # @param [Symbol] method_name The name of the method of the registered plugin +plugin_name+ to call. + # @return [true] def bind( plugin_name, method_name ) + plugin_name = plugin_name.to_sym unless plugin_name.class == Symbol + method_name = method_name.to_sym unless method_name.class == Symbol @members[plugin_name] = [] unless @members.has_key?( plugin_name ) @members[plugin_name].push( method_name ) unless @members[plugin_name].include?( method_name ) return true end - - # Releases the binding of the value, both params as - # in bind, but optional (false = 'wildcard') + + # Releases the responder of the value, both params as in bind, but optional +method_name+ can be omitted, matching all methods bound to the +plugin_name+. + # @param [Symbol] plugin_name The name of the plugin acting as a responder to the value. + # @param [Symbol] method_name The name of the method of the plugin acting as a responder to the value. + # @return [Boolean] Returns true, if successful, false if not bound or other error. def release( plugin_name=false, method_name=false ) + plugin_name = plugin_name.to_sym if plugin_name.class == String + method_name = method_name.to_sym if method_name.class == String return release_all if not plugin_name and not method_name return false unless @members.has_key?( plugin_name ) if not method_name @members.delete( plugin_name ) else @members[plugin_name].slice!(@members[plugin_name].index( method_name )) if @members[plugin_name].include?(method_name) end return true end - - ## Releases all members. + + # Releases all responders. + # @return [true] def release_all @members = {} return true end - - # The unbind method can be used as an alias to release (as in the client). + + # @deprecated Use {#release} as the opposite to bind. alias unbind release - - # Tell all bound instances that the value is changed. + + # @private Tell all bound instances that the value is changed. def tell( msg ) invalid_count = 0 @members.each_key do |plugin_name| @members[plugin_name].each do |method_name| invalid_count += 1 unless msg.plugins.run_plugin( plugin_name, method_name, msg, self ) end end if invalid_count == 0 @is_valid = true - msg.session[:values][:check].delete( @val_id ) + msg.session[:values][:check].delete( @value_id ) end end - - # Handle client updates. - def from_client( msg, data ) + # @private Handle updates from the client. + def from_client( msg, data ) + # only process changes, if different from the one already stored. if @data != data - + ## set takes care of the setting.. @data = data - + ## change the valid state, because the value was set by the client! @is_valid = false - + ## add the id to the values to be checked check_ids = msg.session[:values][:check] - unless check_ids.include?( @val_id ) - check_ids.push( @val_id ) + unless check_ids.include?( @value_id ) + check_ids.push( @value_id ) end end - + end - + + # @private Adds the value to the sync array. def add_to_sync( msg ) - ## add the id to the values to be syncronized (to client) + ## add the id to the values to be synchronized (to client) sync_ids = msg.session[:values][:sync] - unless sync_ids.include?( @val_id ) - sync_ids.push( @val_id ) + unless sync_ids.include?( @value_id ) + sync_ids.push( @value_id ) end end - - # Sets the data. - def set( msg, data, dont_tell_client=false ) + # Sets the data of the value, the change will be synced with the client. + # @param [Message] msg The {Message} instance. + # @param [#to_json] data Any data that can be mapped to JSON and handled by the client. + # @param [Boolean] dont_tell_client Doesn't notify the client about the change, if true. + def set( msg, data, dont_tell_client=false ) + @data = data - + # won't tell the client about the change, usually not needed unless dont_tell_client ## update the flags @sync = false @is_valid = true - + add_to_sync( msg ) end end - - # Tell the client that the value changed. + + # @private Tell the client that the value changed. def to_client( msg ) if @is_new_to_client ## Initialize a new client value - init_str = "COMM.Values.create(#{@val_id.to_json},#{@data.to_json});" + init_str = "COMM.Values.create(#{@value_id.to_json},#{@data.to_json});" msg.reply_value( init_str ) @is_new_to_client = false else ## Sets the client value - msg.reply_value "HVM.s(#{@val_id.to_json},#{@data.to_json});" + msg.reply_value "HVM.s(#{@value_id.to_json},#{@data.to_json});" end end - - # Clean up self. - def die( msg=false ) + # Destructor method. If msg is supplied, deletes the client representation too. + # @param [false, Message] A {Message} instance. When supplied, deletes the client representation. + def die!( msg=false ) + release_all - + # get the value storage from the session data session_values = msg.session[:values][:by_id] - + ## Store the object here - session_values.delete( @val_id ) - + session_values.delete( @value_id ) + if msg and not @is_new_to_client - msg.reply_value("HVM.del(#{@val_id.to_json});") + msg.reply_value("HVM.del(#{@value_id.to_json});") end end - + alias die die! + end + +=begin class UploadValue < HValue @state_responders = { :ready => [], # id == 0 :started => [], # id == 1 @@ -215,14 +265,14 @@ def from_client( msg, data ) ## change the valid state, because the value was set by the client! @is_valid = data.include?(':::') - # the state and key are separated by the ':::' delimitter string + # the state and key are separated by the ':::' delimiter string if @is_valid - # split state and key using the delimitter + # split state and key using the delimiter (upload_state, upload_key) = data.split(':::') # the state is a number upload_state = upload_state.to_i @@ -231,34 +281,34 @@ # negative states are errors if upload_state < 0 # "upload error: #{upload_state}" # (parse the error) - unless @state_respders[:error].empty? + unless @state_responders[:error].empty? @state_responders[:error].each do |plugin_name,method_name| msg.run( plugin_name,method_name,msg,self,upload_state ) end end - # the default state, 0 means the ui is ready to send an + # the default state, 0 means the UI is ready to send an # upload and ticketserve is ready to receive it elsif upload_state == 0 # "upload state: ready to upload." # (do nothing) - unless @state_respders[:ready].empty? + unless @state_responders[:ready].empty? @state_responders[:ready].each do |plugin_name,method_name| msg.run( plugin_name,method_name,msg,self,upload_state ) end end # this state means the upload's transfer is started and progressing elsif upload_state == 1 # "upload state: upload started." # (show progress bar) - unless @state_respders[:started].empty? + unless @state_responders[:started].empty? @state_responders[:started].each do |plugin_name,method_name| msg.run( plugin_name,method_name,msg,self,upload_state ) end end @@ -276,12 +326,12 @@ @data = uploaded_data ## add the id to the values to be checked check_ids = msg.session[:values][:check] - unless check_ids.include?( @val_id ) - check_ids.push( @val_id ) + unless check_ids.include?( @value_id ) + check_ids.push( @value_id ) end end msg.plugins[:ticketservices].del_uploads(upload_key,msg.ses_id) else @@ -319,9 +369,10 @@ def setup_upload(msg,hvalue,size_bytes=500*1024,accept_mime=/image\/(.*?)/,allow_multi=false) upload_key = msg.plugins[:ticketservices].upload_key(msg,hvalue.val_id,size_bytes,accept_mime,allow_multi) hvalue.set( msg, upload_key ) end end +=end end