revent 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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