revent 0.1 → 0.2

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.
@@ -0,0 +1,33 @@
1
+ class String
2
+
3
+ def to_snake! # no one should change these unless they can benchmark and prove their way is faster. =)
4
+ @cached_snake_strings ||= {}
5
+ @cached_snake_strings[self] ||= (
6
+ while x = index(/([a-z\d])([A-Z])/) # unfortunately have to use regex for this one
7
+ y=x+1
8
+ self[x..y] = self[x..x]+"_"+self[y..y].downcase
9
+ end
10
+ self
11
+ )
12
+ end
13
+
14
+ # Aryk: This might be a better way of writing it. I made a lightweight version to use in my modifications. Feel free to adapt this to the rest.
15
+ def to_camel! # no one should change these unless they can benchmark and prove their way is faster. =)
16
+ @cached_camel_strings ||= {}
17
+ @cached_camel_strings[self] ||= (
18
+ # new_string = self.dup # need to do this since sometimes the string is frozen
19
+ while x = index("_")
20
+ y=x+1
21
+ self[x..y] = self[y..y].capitalize # in my tests, it was faster than upcase
22
+ end
23
+ self
24
+ )
25
+ end
26
+
27
+ def to_title
28
+ title = self.dup
29
+ title[0..0] = title[0..0].upcase
30
+ title
31
+ end
32
+
33
+ end
@@ -0,0 +1,121 @@
1
+ module RubyAMF
2
+ module VoHelper
3
+ class VoHash < Hash
4
+ attr_accessor :_explicitType
5
+ end
6
+
7
+ require "#{File.dirname(__FILE__)}/../app/configuration" # cant put this at the top because VoHash has to be instantiated for app/configuration to work
8
+ class VoUtil
9
+
10
+ include RubyAMF::Configuration
11
+ include RubyAMF::Exceptions
12
+
13
+ #moved logic here so AMF3 and AMF0 can use it
14
+ def self.get_vo_for_incoming(obj,action_class_name)
15
+ if (mapping = ClassMappings.get_vo_mapping_for_actionscript_class(action_class_name)) || ## if there is a map use, that class
16
+ (ruby_obj = ClassMappings.assume_types&&(action_class_name.constantize.new rescue false)) # if no map, and try to get the assumed type
17
+ if mapping #if there's a map, then we default to it's specification.
18
+ obj.reject!{|k,v| mapping[:ignore_fields][k]}
19
+ ruby_obj = mapping[:ruby].constantize.new
20
+ end
21
+ if ruby_obj.is_a?(ActiveRecord::Base) # put all the attributes fields into the attribute instance variable
22
+ attributes = {} # extract attributes
23
+ if mapping
24
+ obj.each_key do |field|
25
+ if ClassMappings.attribute_names[mapping[:ruby]][field]
26
+ attributes[field] = obj.delete(field)
27
+ end
28
+ end
29
+ else # for assumed types when there is no mapping
30
+ attribs = (ruby_obj.attribute_names + ["id"]).inject({}){|hash, attr| hash[attr]=true ; hash}
31
+ obj.each_key do |field|
32
+ if attribs[field]
33
+ attributes[field] = obj.delete(field)
34
+ end
35
+ end
36
+ end
37
+ attributes.delete("id") if attributes["id"]==0 # id attribute cannot be zero
38
+ ruby_obj.instance_variable_set("@attributes", attributes) # bypasses any overwriting of the attributes=(value) method (also allows 'id' to be set)
39
+ ruby_obj.instance_variable_set("@new_record", false) if attributes["id"] # the record already exists in the database
40
+ obj.each_key do |field|
41
+ if reflection = ruby_obj.class.reflections[field.to_sym] # is it an association
42
+ value = obj.delete(field) # get rid of the field so it doesnt get added in the next loop
43
+ case reflection.macro
44
+ when :has_one
45
+ ruby_obj.send("set_#{field}_target", value)
46
+ when :belongs_to
47
+ ruby_obj.send("#{field}=", value)
48
+ when :has_many, :has_many_and_belongs_to
49
+ ruby_obj.send("#{field}").target = value
50
+ when :composed_of
51
+ ruby_obj.send("#{field}=", value) # this sets the attributes to the corresponding values
52
+ end
53
+ end
54
+ end
55
+ end
56
+ obj.each do |field, value| # whatever is left, set them as instance variables in the object
57
+ ruby_obj.instance_variable_set("@#{field}", value)
58
+ end
59
+ ruby_obj
60
+ else # then we are still left with a normal hash, lets see if we need to change the type of the keys
61
+ case ClassMappings.hash_key_access
62
+ when :symbol : obj.symbolize_keys!
63
+ when :string : obj # by default the keys are a string type, so just return the obj
64
+ when :indifferent : HashWithIndifferentAccess.new(obj)
65
+ # else # TODO: maybe add a raise FlexError since they somehow put the wrong value for this feature
66
+ end
67
+ end
68
+ end
69
+
70
+ # Aryk: I tried to make this more efficent and clean.
71
+ def self.get_vo_hash_for_outgoing(obj)
72
+ new_object = VoHash.new #use VoHash because one day, we might do away with the class Object patching
73
+ instance_vars = obj.instance_variables
74
+ if map = ClassMappings.get_vo_mapping_for_ruby_class(obj.class.to_s)
75
+ if map[:type]=="active_record"
76
+ attributes_hash = obj.attributes
77
+ (map[:attributes]||attributes_hash.keys).each do |attr| # need to use dup because sometimes the attr is frozen from the AR attributes hash
78
+ attr_name = attr
79
+ attr_name = attr_name.dup.to_camel! if ClassMappings.translate_case # need to do it this way because the string might be frozen if it came from the attributes_hash.keys
80
+ new_object[attr_name] = attributes_hash[attr]
81
+ end
82
+ instance_vars = [] # reset the instance_vars for the associations, this way no unwanted instance variables (ie @new_record, @read_only) can get through
83
+ # Note: if you did not specify associations, it will not show up even if you eager loaded them.
84
+ if map[:associations] # Aryk: if they opted for assocations, make sure that they are loaded in. This is great for composed_of, since it cannot be included on a find
85
+ map[:associations].each do |assoc|
86
+ instance_vars << ("@"+assoc) if obj.send(assoc) # this will make sure they are instantiated and only load it if they have a value.
87
+ end
88
+ elsif ClassMappings.check_for_associations
89
+ instance_vars = obj.instance_variables.reject{|assoc| ["@attributes","@new_record","@read_only"].include?(assoc)}
90
+ end
91
+ end
92
+ new_object._explicitType = map[:actionscript] # Aryk: This only works on the Hash because rubyAMF extended class Object to have this accessor, probably not the best idea, but its already there.
93
+ # Tony: There's some duplication in here. Had trouble consolidating the logic though. Ruby skills failed.
94
+ elsif ClassMappings.assume_types
95
+ new_object._explicitType = obj.class.to_s
96
+ if obj.is_a?(ActiveRecord::Base)
97
+ obj.attributes.keys.each do |key|
98
+ attr_name = key
99
+ attr_name = attr_name.dup.to_camel! if ClassMappings.translate_case # need to do it this way because the string might be frozen if it came from the attributes_hash.keys
100
+ new_object[attr_name] = obj.attributes[key]
101
+ end
102
+ instance_vars = []
103
+ if ClassMappings.check_for_associations
104
+ instance_vars = obj.instance_variables.reject{|assoc| ["@attributes","@new_record","@read_only"].include?(assoc)}
105
+ end
106
+ end
107
+ end
108
+ instance_vars.each do |var| # this also picks up the eager loaded associations, because association called "has_many_assoc" has an instance variable called "@has_many_assoc"
109
+ attr_name = var[1..-1]
110
+ attr_name.to_camel! if ClassMappings.translate_case
111
+ new_object[attr_name] = obj.instance_variable_get(var)
112
+ end
113
+ new_object
114
+ rescue Exception => e
115
+ puts e.message
116
+ puts e.backtrace
117
+ raise RUBYAMFException.new(RUBYAMFException.VO_ERROR, e.message)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,178 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require "#{File.dirname(__FILE__)}/amf3/amf3"
4
+
5
+ # AS clients are not reliable. For security, we close the connection immediately
6
+ # if there is any error.
7
+ module Revent
8
+ module ASRCon
9
+ # Called by Flash player
10
+ CMD_POLICY = '<policy-file-request/>' + "\0"
11
+
12
+ TYPE_CALL = 0
13
+ TYPE_RESULT = 1
14
+ TYPE_ERROR = 2
15
+
16
+ attr_writer :me
17
+ attr_accessor :property # Something that you want to associate with this connection
18
+ attr_writer :cons
19
+
20
+ def post_init
21
+ @connected = true
22
+ @data = ''
23
+ @dec = RubyAMF::IO::AMFDeserializer.new
24
+ @enc = RubyAMF::IO::AMFSerializer.new
25
+ end
26
+
27
+ def receive_data(data)
28
+ @data << data
29
+ if @data == CMD_POLICY
30
+ send_data(@me.policy + "\0")
31
+ close_connection_after_writing
32
+ return
33
+ end
34
+
35
+ if @data.size > @me.max_cmd_length
36
+ close_connection
37
+ return
38
+ end
39
+
40
+ while true
41
+ @dec.reset
42
+ @dec.stream = @data
43
+ o = @dec.read_amf3
44
+ break if o.nil?
45
+
46
+ process(o)
47
+
48
+ last_size = @data.size
49
+ @data.slice!(0, @dec.stream_position)
50
+ break if last_size == @dec.stream_position
51
+ end
52
+ rescue
53
+ close_connection
54
+ end
55
+
56
+ def process(o)
57
+ type = o[0]
58
+ cmd = o[1]
59
+ value = o[2]
60
+
61
+ case type
62
+ when TYPE_CALL
63
+ @me.on_call(self, cmd, value)
64
+ when TYPE_RESULT
65
+ @me.on_result(self, cmd, value)
66
+ when TYPE_ERROR
67
+ @me.on_error(self, cmd, value)
68
+ end
69
+ rescue => e
70
+ @me.logger.error(e)
71
+ close_connection
72
+ end
73
+
74
+ def unbind
75
+ @connected = false
76
+ if @cons.nil?
77
+ @me.on_close
78
+ else
79
+ @me.on_close(self)
80
+ @cons.delete(self)
81
+ end
82
+ end
83
+
84
+ # ----------------------------------------------------------------------------
85
+
86
+ def send_amf3_data(data)
87
+ @enc.reset
88
+ @enc.write_amf3(data)
89
+ send_data(@enc.stream)
90
+ @enc.reset
91
+ end
92
+
93
+ def connected?
94
+ @connected
95
+ end
96
+
97
+ # IP of the container.
98
+ def remote_ip
99
+ peername = get_peername
100
+ return @last_ip if peername.nil?
101
+
102
+ a = get_peername[2,6].unpack("nC4")
103
+ a.delete_at(0)
104
+ @last_ip = a.join('.')
105
+ end
106
+
107
+ def call(cmd, value, close_connection_after_writing = false)
108
+ send_amf3_data([TYPE_CALL, cmd, value])
109
+ self.close_connection_after_writing if close_connection_after_writing
110
+ end
111
+
112
+ def result(cmd, value, close_connection_after_writing = false)
113
+ send_amf3_data([TYPE_RESULT, cmd, value])
114
+ self.close_connection_after_writing if close_connection_after_writing
115
+ rescue
116
+ end
117
+
118
+ def error(cmd, value, close_connection_after_writing = false)
119
+ send_amf3_data([TYPE_ERROR, cmd, value])
120
+ self.close_connection_after_writing if close_connection_after_writing
121
+ end
122
+ end
123
+
124
+ # To make your class a server, just include Revent::RRServer in it.
125
+ module ASRServer
126
+ # For security check, normally command cannot be too long
127
+ DEFAULT_MAX_CMD_LENGTH = 1024
128
+
129
+ attr_accessor :logger, :max_cmd_length, :policy
130
+
131
+ # Set logger, max_cmd_length, policy if you don't want the default values
132
+ # before calling this method.
133
+ def start_server(host, port)
134
+ @logger = Logger.new(STDOUT) if @logger.nil?
135
+ @max_cmd_length = DEFAULT_MAX_CMD_LENGTH if @max_cmd_length.nil?
136
+ if @policy.nil?
137
+ @policy = <<EOF
138
+ <cross-domain-policy>
139
+ <allow-access-from domain="*" to-ports="#{port}" />
140
+ </cross-domain-policy>
141
+ EOF
142
+ end
143
+
144
+ @revent_cons = []
145
+ EventMachine::start_server(host, port, ASRCon) do |con|
146
+ @revent_cons << con
147
+ con.me = self
148
+ con.cons = @revent_cons
149
+ on_connect(con)
150
+ end
151
+ end
152
+
153
+ # Utilities. You can call remote_ip, call, close directly on each "client", as
154
+ # if it is an instance of Revent::RRClient.
155
+ # ----------------------------------------------------------------------------
156
+
157
+ def clients
158
+ @revent_cons
159
+ end
160
+
161
+ # ----------------------------------------------------------------------------
162
+
163
+ def on_connect(client)
164
+ end
165
+
166
+ def on_close(client)
167
+ end
168
+
169
+ def on_call(client, cmd, value)
170
+ end
171
+
172
+ def on_result(client, cmd, value)
173
+ end
174
+
175
+ def on_error(client, cmd, value)
176
+ end
177
+ end
178
+ end
data/lib/revent/r_r.rb ADDED
@@ -0,0 +1,176 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'stringio'
4
+
5
+ module Revent
6
+ # Included by both Revent::RRServer and Revent::RRClient.
7
+ module RRCon
8
+ TYPE_CALL = 0
9
+ TYPE_RESULT = 1
10
+ TYPE_ERROR = 2
11
+
12
+ attr_writer :me
13
+ attr_accessor :property # Something that you want to associate with this connection
14
+ attr_writer :cons # Only used for Revent::RRServer
15
+
16
+ def post_init
17
+ @connected = true
18
+ @data = ''
19
+ end
20
+
21
+ def receive_data(data)
22
+ @data << data
23
+ while true
24
+ io = StringIO.new(@data)
25
+ o = Marshal.load(io)
26
+ @data.slice!(0, io.pos)
27
+ process(o)
28
+ end
29
+ rescue
30
+ end
31
+
32
+ def process(o)
33
+ type = o[0]
34
+ cmd = o[1]
35
+ value = o[2]
36
+
37
+ case type
38
+ when TYPE_CALL
39
+ value = @cons.nil? ? @me.on_call(cmd, value) : @me.on_call(self, cmd, value)
40
+ o = [TYPE_RESULT, cmd, value]
41
+ send_data(Marshal.dump(o))
42
+ when TYPE_RESULT
43
+ @cons.nil? ? @me.on_result(cmd, value) : @me.on_result(self, cmd, value)
44
+ when TYPE_ERROR
45
+ @cons.nil? ? @me.on_error(cmd, value) : @me.on_error(self, cmd, value)
46
+ end
47
+ rescue => e
48
+ o = [TYPE_ERROR, cmd, e]
49
+ send_data(Marshal.dump(o))
50
+ end
51
+
52
+ def unbind
53
+ @connected = false
54
+ if @cons.nil?
55
+ @me.on_close
56
+ else
57
+ @me.on_close(self)
58
+ @cons.delete(self)
59
+ end
60
+ end
61
+
62
+ # ----------------------------------------------------------------------------
63
+
64
+ def connected?
65
+ @connected
66
+ end
67
+
68
+ # IP of the container.
69
+ def remote_ip
70
+ peername = get_peername
71
+ return @last_ip if peername.nil?
72
+
73
+ a = get_peername[2,6].unpack("nC4")
74
+ a.delete_at(0)
75
+ @last_ip = a.join('.')
76
+ end
77
+
78
+ def call(cmd, value)
79
+ o = [TYPE_CALL, cmd, value]
80
+ send_data(Marshal.dump(o))
81
+ end
82
+ end
83
+
84
+
85
+ # To make your class a server, just include Revent::RRServer in it.
86
+ module RRServer
87
+ def start_server(host, port)
88
+ @revent_cons = []
89
+ EventMachine::start_server(host, port, RRCon) do |con|
90
+ @revent_cons << con
91
+ con.me = self
92
+ con.cons = @revent_cons
93
+ on_connect(con)
94
+ end
95
+ end
96
+
97
+ # Utilities. You can call remote_ip, call, close directly on each "client", as
98
+ # if it is an instance of Revent::RRClient.
99
+ # ----------------------------------------------------------------------------
100
+
101
+ def clients
102
+ @revent_cons
103
+ end
104
+
105
+ # ----------------------------------------------------------------------------
106
+
107
+ def on_connect(client)
108
+ end
109
+
110
+ def on_close(client)
111
+ end
112
+
113
+ def on_call(client, cmd, value)
114
+ end
115
+
116
+ def on_result(client, cmd, value)
117
+ end
118
+
119
+ def on_error(client, cmd, value)
120
+ end
121
+ end
122
+
123
+ # To make your class a client, just include Revent::RRClient in it.
124
+ module RRClient
125
+ def connect(host, port)
126
+ EventMachine::connect(host, port, RRCon) do |con|
127
+ @revent_con = con
128
+ con.me = self
129
+ on_connect
130
+ end
131
+ end
132
+
133
+ # Utilities ------------------------------------------------------------------
134
+
135
+ def property
136
+ @revent_con.property
137
+ end
138
+
139
+ def property=(value)
140
+ @revent_con.property = value
141
+ end
142
+
143
+ def remote_ip
144
+ @revent_con.remote_ip
145
+ end
146
+
147
+ def call(cmd, value)
148
+ @revent_con.call(cmd, value)
149
+ end
150
+
151
+ def close_connection
152
+ @revent_con.close_connection
153
+ end
154
+
155
+ def close_connection_after_writing
156
+ @revent_con.close_connection_after_writing
157
+ end
158
+
159
+ # ----------------------------------------------------------------------------
160
+
161
+ def on_connect
162
+ end
163
+
164
+ def on_close
165
+ end
166
+
167
+ def on_call(cmd, value)
168
+ end
169
+
170
+ def on_result(cmd, value)
171
+ end
172
+
173
+ def on_error(cmd, value)
174
+ end
175
+ end
176
+ end