flotype-bridge 0.1.0.beta.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.
Files changed (60) hide show
  1. data/README.md +6 -0
  2. data/Rakefile +24 -0
  3. data/doc/Bridge.html +954 -0
  4. data/doc/Bridge/Callback.html +495 -0
  5. data/doc/Bridge/CallbackRef.html +343 -0
  6. data/doc/Bridge/Conn.html +409 -0
  7. data/doc/Bridge/Core.html +762 -0
  8. data/doc/Bridge/LocalRef.html +509 -0
  9. data/doc/Bridge/Ref.html +647 -0
  10. data/doc/Bridge/Service.html +135 -0
  11. data/doc/Bridge/Sys.html +278 -0
  12. data/doc/Bridge/Util.html +746 -0
  13. data/doc/_index.html +202 -0
  14. data/doc/class_list.html +47 -0
  15. data/doc/classes/Bridge.html +263 -0
  16. data/doc/classes/Bridge.src/M000001.html +24 -0
  17. data/doc/classes/Bridge.src/M000002.html +18 -0
  18. data/doc/classes/Bridge.src/M000003.html +18 -0
  19. data/doc/classes/Bridge.src/M000004.html +20 -0
  20. data/doc/classes/Bridge.src/M000005.html +26 -0
  21. data/doc/classes/Bridge.src/M000006.html +22 -0
  22. data/doc/classes/Bridge.src/M000007.html +18 -0
  23. data/doc/classes/Bridge.src/M000008.html +18 -0
  24. data/doc/created.rid +1 -0
  25. data/doc/css/common.css +1 -0
  26. data/doc/css/full_list.css +55 -0
  27. data/doc/css/style.css +322 -0
  28. data/doc/file.README.html +71 -0
  29. data/doc/file_list.html +49 -0
  30. data/doc/files/lib/bridge_rb.html +108 -0
  31. data/doc/fr_class_index.html +27 -0
  32. data/doc/fr_file_index.html +27 -0
  33. data/doc/fr_method_index.html +34 -0
  34. data/doc/frames.html +13 -0
  35. data/doc/index.html +71 -0
  36. data/doc/js/app.js +205 -0
  37. data/doc/js/full_list.js +173 -0
  38. data/doc/js/jquery.js +16 -0
  39. data/doc/method_list.html +510 -0
  40. data/doc/rdoc-style.css +208 -0
  41. data/doc/top-level-namespace.html +105 -0
  42. data/examples/chat/chat_client.rb +41 -0
  43. data/examples/chat/chat_server.rb +24 -0
  44. data/examples/pong/pong.rb +29 -0
  45. data/flotype-bridge.gemspec +24 -0
  46. data/lib/bb/callback.rb +33 -0
  47. data/lib/bb/conn.rb +31 -0
  48. data/lib/bb/core.rb +109 -0
  49. data/lib/bb/localref.rb +37 -0
  50. data/lib/bb/ref.rb +49 -0
  51. data/lib/bb/svc.rb +7 -0
  52. data/lib/bb/sys.rb +17 -0
  53. data/lib/bb/util.rb +93 -0
  54. data/lib/bb/version.rb +3 -0
  55. data/lib/bridge.rb +130 -0
  56. data/rakelib/package.rake +4 -0
  57. data/rakelib/test.rake +8 -0
  58. data/tests/bb_test_helper.rb +3 -0
  59. data/tests/test_serialize.rb +4 -0
  60. metadata +196 -0
data/lib/bb/conn.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Bridge
2
+ module Conn
3
+ def self.send arg
4
+ Util::log 'sending: ' + arg
5
+ @@conn.send_data arg
6
+ end
7
+
8
+ # Methods expected by EventMachine.
9
+ def initialize
10
+ Util::log 'Connection initialized.'
11
+ @@conn = self
12
+ end
13
+
14
+ def post_init
15
+ Util::log 'Starting handshake.'
16
+ Core::command(:CONNECT,
17
+ { :session => Core::session,
18
+ :api_key => Bridge::options['api_key'] })
19
+ end
20
+
21
+ def receive_data data
22
+ Util::log 'Got data.'
23
+ Core::process(data)
24
+ end
25
+
26
+ def unbind
27
+ Util::log 'Disconnected.'
28
+ Core::disconnect
29
+ end
30
+ end
31
+ end
data/lib/bb/core.rb ADDED
@@ -0,0 +1,109 @@
1
+ module Bridge
2
+ # The internals of the Bridge client. Use of this module is strongly
3
+ # unadvised, as the internal structure may vary greatly from that of
4
+ # other language implementations.
5
+ module Core
6
+ @@services, @@queue, @@sess = {'["system"]' => Bridge::Sys}, [], [nil, nil]
7
+ @@connected, @@len, @@buffer = false, 0, ''
8
+
9
+ def self.session
10
+ @@sess
11
+ end
12
+
13
+ def self.client_id
14
+ @@sess[0]
15
+ end
16
+
17
+ def self.connected
18
+ @@connected
19
+ end
20
+
21
+ # The queue is used primarily for Bridge::ready() callbacks.
22
+ def self.enqueue fun
23
+ if @@connected
24
+ fun.call
25
+ else
26
+ @@queue = @@queue + [fun]
27
+ end
28
+ end
29
+
30
+ def self.store svc, obj = nil, named = true
31
+ @@services[[named ? svc : "channel:#{svc}"].to_json] = obj
32
+ end
33
+
34
+ def self.lookup ref
35
+ Util::log 'Looking up ref ' + ref.to_json
36
+ ref = JSON::parse ref.to_json
37
+ svc = @@services[ref[2 .. -1].to_json] || @@services[[ref[2]].to_json]
38
+ if svc != nil
39
+ if ref[3] != nil
40
+ svc.method(ref[3])
41
+ else
42
+ svc
43
+ end
44
+ else
45
+ Ref.lookup ref
46
+ end
47
+ end
48
+
49
+ def self.process data
50
+ if @@len == 0
51
+ @@len = data.unpack('N')[0]
52
+ return process data[4 .. -1]
53
+ end
54
+ Util::log ':::: ' + data
55
+ (@@buffer << data)
56
+ if @@buffer.length < @@len
57
+ return
58
+ end
59
+ data = @@buffer
60
+ @@buffer, @@len = @@buffer[@@len .. -1], 0
61
+ # If this is the first message, set our SessionId and Secret.
62
+ m = /^(\w+)\|(\w+)$/.match data
63
+ if m
64
+ @@connected = true
65
+ @@sess = [m[1], m[2]]
66
+ Util::log "Received secret and session ID: #{@@sess.to_json}"
67
+ @@queue.each {|fun|
68
+ fun.call
69
+ }
70
+ @@queue = []
71
+ return
72
+ end
73
+ # Else, it is a normal message.
74
+ unser = Util::unserialize data
75
+ dest = unser['destination']
76
+ if dest.respond_to? :call
77
+ dest.call *unser['args']
78
+ end
79
+ end
80
+
81
+ def self.command cmd, data
82
+ if cmd == :CONNECT
83
+ Conn::send(Util::serialize({:command => cmd, :data => data}))
84
+ else
85
+ Core::enqueue lambda {
86
+ Conn::send(Util::serialize({:command => cmd, :data => data}))
87
+ }
88
+ end
89
+ end
90
+
91
+ def self.reconnect timeout
92
+ opts = Bridge::options
93
+ if opts[:reconnect]
94
+ Util::log "Attempting to reconnect; waiting at most #{timeout.to_s}s."
95
+ EventMachine::connect(opts[:host], opts[:port], Conn)
96
+ EventMachine::Timer.new(timeout) do
97
+ if not @@connected
98
+ reconnect timeout * 2
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def self.disconnect
105
+ @@connected = false
106
+ reconnect 0.1
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,37 @@
1
+ module Bridge
2
+ # Wrapper for callbacks passed in as arguments.
3
+ class LocalRef < Ref
4
+ def initialize path, mod
5
+ @mod = mod
6
+ path = ['client', lambda {Core::client_id}] + path
7
+ super(path)
8
+ end
9
+
10
+ def method_missing atom, *args, &blk
11
+ (@mod.method atom).call *args
12
+ end
13
+
14
+ def method atom
15
+ @mod.method(atom)
16
+ end
17
+
18
+ def operations bool
19
+ @mod.methods(bool)
20
+ end
21
+
22
+ def call *args
23
+ @mod.call *args
24
+ end
25
+
26
+ def respond_to? atom
27
+ @mod.respond_to? atom
28
+ end
29
+
30
+ def to_json *a
31
+ if @path[1].respond_to? :call
32
+ @path[1] = @path[1].call
33
+ end
34
+ {:ref => @path, :operations => @mod.methods(false)}.to_json *a
35
+ end
36
+ end
37
+ end
data/lib/bb/ref.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Bridge
2
+ # Instances of this class represent references to remote services.
3
+ class Ref
4
+ @@refs = {}
5
+
6
+ def self.lookup ref
7
+ key = ref[2 .. -1].to_json
8
+ if @@refs[key] == nil
9
+ @@refs[key] = Ref.new(ref)
10
+ end
11
+ @@refs[key]
12
+ end
13
+
14
+ def initialize path
15
+ @path = path
16
+ @@refs[path[2 .. -1].to_json] = self
17
+ end
18
+
19
+ def path
20
+ @path
21
+ end
22
+
23
+ def method_missing atom, *args, &blk
24
+ Core::lookup(@path + [atom]).call *args
25
+ end
26
+
27
+ def method atom
28
+ Core::lookup(@path + [atom])
29
+ end
30
+
31
+ def call *args
32
+ Core::command :SEND, {
33
+ :destination => {:ref => @path},
34
+ :args => Util::inflate(args)
35
+ }
36
+ end
37
+
38
+ def respond_to? atom
39
+ true
40
+ end
41
+
42
+ def to_json *a
43
+ if @path[1].respond_to? :call
44
+ @path[1] = @path[1].call
45
+ end
46
+ {:ref => @path}.to_json *a
47
+ end
48
+ end
49
+ end
data/lib/bb/svc.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Bridge
2
+ # Wrapper for user-defined Bridge Services. All services passed in
3
+ # as arguments must include this module in order to be detected and
4
+ # serialized correctly.
5
+ module Service
6
+ end
7
+ end
data/lib/bb/sys.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Bridge
2
+ # These are internal system functions, which should only be called by the
3
+ # Erlang gateway.
4
+ module Sys
5
+ def self.hook_channel_handler name, handler, fun
6
+ fun.call(Core::store(name, handler, 'channel'))
7
+ end
8
+
9
+ def self.remoteError msg
10
+ Util::err(msg)
11
+ end
12
+
13
+ def self.get_service name, fun
14
+ fun.call(Core::lookup(name).methods(false))
15
+ end
16
+ end
17
+ end
data/lib/bb/util.rb ADDED
@@ -0,0 +1,93 @@
1
+ require 'json'
2
+
3
+ module Bridge
4
+ module Util
5
+ # Traverses an object, replacing funs with refs.
6
+ def self.inflate obj
7
+ if obj.is_a?(Array)
8
+ obj.map do |v|
9
+ Util::bloat v
10
+ end
11
+ else
12
+ o = {}
13
+ obj.each do |k, v|
14
+ o[k] = Util::bloat v
15
+ end
16
+ o
17
+ end
18
+ end
19
+
20
+ def self.bloat v
21
+ if v.is_a?(Module) || v.is_a?(Bridge::Service)
22
+ local_ref(v)
23
+ elsif v.respond_to?(:call) && !v.is_a?(Ref)
24
+ cb(v)
25
+ elsif v.is_a?(Hash) || v.is_a?(Array)
26
+ inflate v
27
+ else
28
+ v
29
+ end
30
+ end
31
+
32
+ def self.deflate obj
33
+ if obj.is_a?(Array)
34
+ obj.map do |v|
35
+ deflate v
36
+ end
37
+ elsif obj.is_a? Hash
38
+ if obj.has_key? 'ref'
39
+ Core::lookup obj['ref']
40
+ else
41
+ o = {}
42
+ obj.each do |k, v|
43
+ o[k] = deflate v
44
+ end
45
+ o
46
+ end
47
+ else
48
+ obj
49
+ end
50
+ end
51
+
52
+ def self.serialize obj
53
+ obj = inflate(obj)
54
+ str = JSON::generate obj
55
+ [str.length].pack("N") + str
56
+ end
57
+
58
+ def self.unserialize str
59
+ obj = deflate(JSON::parse str)
60
+ deflate obj
61
+ end
62
+
63
+ def self.err msg
64
+ $stderr.puts msg
65
+ end
66
+
67
+ def self.log msg, level = 3
68
+ opts = Bridge::options
69
+ if level <= opts['log_level']
70
+ puts msg
71
+ end
72
+ end
73
+
74
+ def self.cb fun
75
+ Core::store(fun.object_id.to_s(36),
76
+ LocalRef.new([fun.hash.to_s(36)], Callback.new(fun)))
77
+ end
78
+
79
+ def self.has_keys? obj, *keys
80
+ keys.each do |k|
81
+ if !obj.has_key?(k) && !obj.has_key?(k.to_sym)
82
+ return false
83
+ end
84
+ end
85
+ true
86
+ end
87
+
88
+ def self.local_ref v
89
+ key = v.object_id.to_s(36)
90
+ Core::store key, Bridge::LocalRef.new([key], v)
91
+ end
92
+ end
93
+ end
data/lib/bb/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Bridge
2
+ VERSION = "0.1.0.beta.2"
3
+ end
data/lib/bridge.rb ADDED
@@ -0,0 +1,130 @@
1
+ require 'bb/svc'
2
+ require 'bb/conn'
3
+ require 'bb/ref'
4
+ require 'bb/sys'
5
+ require 'bb/core'
6
+ require 'bb/localref'
7
+ require 'bb/callback'
8
+ require 'bb/util'
9
+
10
+ require 'eventmachine'
11
+
12
+ module Bridge
13
+
14
+ # Expects to be called inside an EventMachine block in lieu of
15
+ # EM::connect.
16
+ # @param [Hash] configuration options
17
+ @options = {
18
+ 'reconnect' => true,
19
+ 'redir_host' => 'redirector.flotype.com',
20
+ 'redir_port' => 80,
21
+ 'log_level' => 3, # 0 for no output.
22
+ }
23
+ def self.initialize(options = {})
24
+ Util::log 'initialize called.'
25
+ @options = @options.merge(options)
26
+
27
+ if !(@options.has_key? 'api_key')
28
+ raise ArgumentError, 'No API key specified.'
29
+ end
30
+
31
+ if Util::has_keys?(@options, 'host', 'port')
32
+ EM::connect(@options['host'], @options['port'], Bridge::Conn)
33
+ else
34
+ # Support for redirector.
35
+ conn = EM::Protocols::HttpClient2.connect(@options['redir_host'],
36
+ @options['redir_port'])
37
+ req = conn.get({:uri => "/redirect/#{@options['api_key']}"})
38
+ req.callback do |obj|
39
+ obj = JSON::parse obj.content
40
+ if obj.has_key?('data')
41
+ obj = obj['data']
42
+ EM::connect(obj['bridge_host'], obj['bridge_port'], Bridge::Conn)
43
+ else
44
+ raise Exception, 'Invalid API key.'
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Accessor for @options.
51
+ # @return [Object] options
52
+ def self.options
53
+ @options
54
+ end
55
+
56
+ # Similar to $(document).ready of jQuery as well as now.ready: takes
57
+ # callbacks that it will call when the connection handshake has been
58
+ # completed.
59
+ # @param [#call] fun Callback to be called when the server connects.
60
+ def self.ready fun
61
+ Core::enqueue fun
62
+ end
63
+
64
+ # Calls a remote function specified by `dest` with `args`.
65
+ # @param [Ref] dest The identifier of the remote function to call.
66
+ # @param [Array] args Arguments to be passed to `dest`.
67
+ def self.send dest, args
68
+ Core::command(:SEND,
69
+ { :args => Util::serialize(args),
70
+ :destination => dest })
71
+ end
72
+
73
+ # Broadcasts the availability of certain functionality specified by a
74
+ # proc `fun` under the name of `svc`.
75
+ def self.publish_service svc, handler, fun = nil
76
+ if svc == 'system'
77
+ Util::err("Invalid service name: #{svc}")
78
+ else
79
+ obj = { :name => svc}
80
+ if fun.respond_to? :call
81
+ obj[:callback] = Util::cb(fun)
82
+ end
83
+ Core::command(:JOINWORKERPOOL, obj)
84
+ Core::store(svc, Bridge::LocalRef.new([svc], handler))
85
+ end
86
+ end
87
+
88
+ # Join the channel specified by `channel`. Messages from this channel
89
+ # will be passed in to a handler specified by `handler`. The callback
90
+ # `fun` is to be called to confirm successful joining of the channel.
91
+ def self.join_channel channel, handler, fun = nil
92
+ obj = { :name => channel, :handler => Util::local_ref(handler)}
93
+ if fun.respond_to? :call
94
+ obj[:callback] = Util::cb(fun)
95
+ end
96
+ Core::command(:JOINCHANNEL, obj)
97
+ end
98
+
99
+ # Leave a channel.
100
+ def self.leave_channel channel, handler, fun = nil
101
+ obj = { :name => channel, :handler => Util::local_ref(handler)}
102
+ if fun.respond_to? :call
103
+ obj[:callback] = Util::cb(fun)
104
+ end
105
+ Core::command(:LEAVECHANNEL, obj)
106
+ end
107
+
108
+ # Leave a channel.
109
+ def self.leave_channel channel, handler, fun
110
+ Core::command(:LEAVECHANNEL,
111
+ { :name => channel,
112
+ :handler => Util::local_ref(handler),
113
+ :callback => Util::cb(fun) })
114
+ end
115
+
116
+ # Returns a reference to the service specified by `svc`.
117
+ def self.get_service svc
118
+ Core::lookup ['named', svc, svc]
119
+ end
120
+
121
+ # Returns a reference to the channel specified by `channel`.
122
+ def self.get_channel channel
123
+ Core::lookup ['channel', channel, "channel:#{channel}"]
124
+ end
125
+
126
+ # The client's ID.
127
+ def self.client_id
128
+ Core::client_id
129
+ end
130
+ end