faye 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of faye might be problematic. Click here for more details.
- data/History.txt +16 -10
- data/README.rdoc +1 -1
- data/lib/faye-browser-min.js +1 -1
- data/lib/faye-browser-min.js.map +2 -2
- data/lib/faye-browser.js +302 -287
- data/lib/faye.rb +21 -21
- data/lib/faye/adapters/rack_adapter.rb +50 -48
- data/lib/faye/adapters/static_server.rb +22 -22
- data/lib/faye/engines/connection.rb +13 -13
- data/lib/faye/engines/memory.rb +21 -21
- data/lib/faye/engines/proxy.rb +23 -23
- data/lib/faye/error.rb +6 -6
- data/lib/faye/mixins/logging.rb +12 -12
- data/lib/faye/mixins/publisher.rb +6 -6
- data/lib/faye/mixins/timeouts.rb +1 -1
- data/lib/faye/protocol/channel.rb +24 -24
- data/lib/faye/protocol/client.rb +71 -73
- data/lib/faye/protocol/extensible.rb +7 -7
- data/lib/faye/protocol/grammar.rb +13 -13
- data/lib/faye/protocol/server.rb +57 -57
- data/lib/faye/protocol/socket.rb +4 -4
- data/lib/faye/protocol/subscription.rb +4 -4
- data/lib/faye/transport/http.rb +13 -13
- data/lib/faye/transport/local.rb +5 -5
- data/lib/faye/transport/transport.rb +25 -25
- data/lib/faye/transport/web_socket.rb +34 -30
- data/lib/faye/util/namespace.rb +4 -4
- data/spec/browser.html +5 -5
- data/spec/javascript/channel_spec.js +3 -3
- data/spec/javascript/client_spec.js +104 -98
- data/spec/javascript/engine/memory_spec.js +1 -1
- data/spec/javascript/engine_spec.js +70 -70
- data/spec/javascript/faye_spec.js +6 -6
- data/spec/javascript/grammar_spec.js +12 -12
- data/spec/javascript/node_adapter_spec.js +46 -46
- data/spec/javascript/publisher_spec.js +4 -4
- data/spec/javascript/server/connect_spec.js +21 -21
- data/spec/javascript/server/disconnect_spec.js +15 -15
- data/spec/javascript/server/extensions_spec.js +6 -6
- data/spec/javascript/server/handshake_spec.js +18 -18
- data/spec/javascript/server/integration_spec.js +23 -23
- data/spec/javascript/server/publish_spec.js +9 -9
- data/spec/javascript/server/subscribe_spec.js +30 -30
- data/spec/javascript/server/unsubscribe_spec.js +30 -30
- data/spec/javascript/server_spec.js +15 -15
- data/spec/javascript/transport_spec.js +32 -27
- data/spec/node.js +2 -2
- data/spec/ruby/channel_spec.rb +2 -2
- data/spec/ruby/client_spec.rb +100 -92
- data/spec/ruby/engine_examples.rb +51 -51
- data/spec/ruby/faye_spec.rb +5 -5
- data/spec/ruby/grammar_spec.rb +12 -12
- data/spec/ruby/publisher_spec.rb +4 -4
- data/spec/ruby/rack_adapter_spec.rb +34 -34
- data/spec/ruby/server/connect_spec.rb +22 -22
- data/spec/ruby/server/disconnect_spec.rb +16 -16
- data/spec/ruby/server/extensions_spec.rb +8 -8
- data/spec/ruby/server/handshake_spec.rb +20 -20
- data/spec/ruby/server/integration_spec.rb +22 -24
- data/spec/ruby/server/publish_spec.rb +9 -9
- data/spec/ruby/server/subscribe_spec.rb +31 -31
- data/spec/ruby/server/unsubscribe_spec.rb +31 -31
- data/spec/ruby/server_spec.rb +17 -17
- data/spec/ruby/transport_spec.rb +23 -23
- data/spec/testswarm +23 -10
- data/spec/thin_proxy.rb +5 -5
- metadata +90 -59
data/lib/faye/engines/memory.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Faye
|
2
2
|
module Engine
|
3
|
-
|
3
|
+
|
4
4
|
class Memory
|
5
5
|
include Timeouts
|
6
|
-
|
6
|
+
|
7
7
|
def self.create(server, options)
|
8
8
|
new(server, options)
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def initialize(server, options)
|
12
12
|
@server = server
|
13
13
|
@options = options
|
@@ -16,7 +16,7 @@ module Faye
|
|
16
16
|
@channels = {}
|
17
17
|
@messages = {}
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def create_client(&callback)
|
21
21
|
client_id = @namespace.generate
|
22
22
|
@server.debug 'Created new client ?', client_id
|
@@ -24,14 +24,14 @@ module Faye
|
|
24
24
|
@server.trigger(:handshake, client_id)
|
25
25
|
callback.call(client_id)
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def destroy_client(client_id, &callback)
|
29
29
|
return unless @namespace.exists?(client_id)
|
30
|
-
|
30
|
+
|
31
31
|
if @clients.has_key?(client_id)
|
32
32
|
@clients[client_id].each { |channel| unsubscribe(client_id, channel) }
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
remove_timeout(client_id)
|
36
36
|
@namespace.release(client_id)
|
37
37
|
@messages.delete(client_id)
|
@@ -39,11 +39,11 @@ module Faye
|
|
39
39
|
@server.trigger(:disconnect, client_id)
|
40
40
|
callback.call if callback
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
def client_exists(client_id, &callback)
|
44
44
|
callback.call(@namespace.exists?(client_id))
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def ping(client_id)
|
48
48
|
timeout = @server.timeout
|
49
49
|
return unless Numeric === timeout
|
@@ -51,45 +51,45 @@ module Faye
|
|
51
51
|
remove_timeout(client_id)
|
52
52
|
add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def subscribe(client_id, channel, &callback)
|
56
56
|
@clients[client_id] ||= Set.new
|
57
57
|
should_trigger = @clients[client_id].add?(channel)
|
58
|
-
|
58
|
+
|
59
59
|
@channels[channel] ||= Set.new
|
60
60
|
@channels[channel].add(client_id)
|
61
|
-
|
61
|
+
|
62
62
|
@server.debug 'Subscribed client ? to channel ?', client_id, channel
|
63
63
|
@server.trigger(:subscribe, client_id, channel) if should_trigger
|
64
64
|
callback.call(true) if callback
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def unsubscribe(client_id, channel, &callback)
|
68
68
|
if @clients.has_key?(client_id)
|
69
69
|
should_trigger = @clients[client_id].delete?(channel)
|
70
70
|
@clients.delete(client_id) if @clients[client_id].empty?
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
if @channels.has_key?(channel)
|
74
74
|
@channels[channel].delete(client_id)
|
75
75
|
@channels.delete(channel) if @channels[channel].empty?
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
@server.debug 'Unsubscribed client ? from channel ?', client_id, channel
|
79
79
|
@server.trigger(:unsubscribe, client_id, channel) if should_trigger
|
80
80
|
callback.call(true) if callback
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
def publish(message, channels)
|
84
84
|
@server.debug 'Publishing message ?', message
|
85
|
-
|
85
|
+
|
86
86
|
clients = Set.new
|
87
|
-
|
87
|
+
|
88
88
|
channels.each do |channel|
|
89
89
|
next unless subs = @channels[channel]
|
90
90
|
subs.each(&clients.method(:add))
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
clients.each do |client_id|
|
94
94
|
@server.debug 'Queueing for client ?: ?', client_id, message
|
95
95
|
@messages[client_id] ||= []
|
@@ -99,14 +99,14 @@ module Faye
|
|
99
99
|
|
100
100
|
@server.trigger(:publish, message['clientId'], message['channel'], message['data'])
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
def empty_queue(client_id)
|
104
104
|
return unless @server.has_connection?(client_id)
|
105
105
|
@server.deliver(client_id, @messages[client_id])
|
106
106
|
@messages.delete(client_id)
|
107
107
|
end
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
data/lib/faye/engines/proxy.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
module Faye
|
2
2
|
module Engine
|
3
|
-
|
3
|
+
|
4
4
|
METHODS = %w[create_client client_exists destroy_client ping subscribe unsubscribe]
|
5
5
|
MAX_DELAY = 0.0
|
6
6
|
INTERVAL = 0.0
|
7
7
|
TIMEOUT = 60.0
|
8
8
|
ID_LENGTH = 160
|
9
|
-
|
9
|
+
|
10
10
|
autoload :Connection, File.expand_path('../connection', __FILE__)
|
11
11
|
autoload :Memory, File.expand_path('../memory', __FILE__)
|
12
|
-
|
12
|
+
|
13
13
|
def self.ensure_reactor_running!
|
14
14
|
Thread.new { EventMachine.run } unless EventMachine.reactor_running?
|
15
15
|
Thread.pass until EventMachine.reactor_running?
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def self.get(options)
|
19
19
|
Proxy.new(options)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def self.random(bitlength = ID_LENGTH)
|
23
23
|
limit = 2 ** bitlength - 1
|
24
24
|
max_size = limit.to_s(36).size
|
@@ -26,32 +26,32 @@ module Faye
|
|
26
26
|
string = '0' + string while string.size < max_size
|
27
27
|
string
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
class Proxy
|
31
31
|
include Publisher
|
32
32
|
include Logging
|
33
|
-
|
33
|
+
|
34
34
|
attr_reader :interval, :timeout
|
35
|
-
|
35
|
+
|
36
36
|
extend Forwardable
|
37
37
|
def_delegators :@engine, *METHODS
|
38
|
-
|
38
|
+
|
39
39
|
def initialize(options)
|
40
40
|
@options = options
|
41
41
|
@connections = {}
|
42
42
|
@interval = @options[:interval] || INTERVAL
|
43
43
|
@timeout = @options[:timeout] || TIMEOUT
|
44
|
-
|
44
|
+
|
45
45
|
engine_class = @options[:type] || Memory
|
46
46
|
@engine = engine_class.create(self, @options)
|
47
|
-
|
47
|
+
|
48
48
|
bind :disconnect do |client_id|
|
49
49
|
EventMachine.next_tick { close_connection(client_id) }
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
debug 'Created new engine: ?', @options
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def connect(client_id, options = {}, &callback)
|
56
56
|
debug 'Accepting connection from ?', client_id
|
57
57
|
@engine.ping(client_id)
|
@@ -59,11 +59,11 @@ module Faye
|
|
59
59
|
conn.connect(options, &callback)
|
60
60
|
@engine.empty_queue(client_id)
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
def has_connection?(client_id)
|
64
64
|
@connections.has_key?(client_id)
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def connection(client_id, create)
|
68
68
|
conn = @connections[client_id]
|
69
69
|
return conn if conn or not create
|
@@ -71,7 +71,7 @@ module Faye
|
|
71
71
|
trigger('connection:open', client_id)
|
72
72
|
@connections[client_id]
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
def close_connection(client_id)
|
76
76
|
debug 'Closing connection for ?', client_id
|
77
77
|
conn = @connections[client_id]
|
@@ -80,13 +80,13 @@ module Faye
|
|
80
80
|
trigger('connection:close', client_id)
|
81
81
|
@connections.delete(client_id)
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
def open_socket(client_id, socket)
|
85
85
|
return unless client_id
|
86
86
|
conn = connection(client_id, true)
|
87
87
|
conn.socket = socket
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def deliver(client_id, messages)
|
91
91
|
return if !messages || messages.empty?
|
92
92
|
conn = connection(client_id, false)
|
@@ -94,28 +94,28 @@ module Faye
|
|
94
94
|
messages.each(&conn.method(:deliver))
|
95
95
|
true
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
def generate_id
|
99
99
|
Engine.random
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
def flush(client_id)
|
103
103
|
return unless client_id
|
104
104
|
debug 'Flushing connection for ?', client_id
|
105
105
|
conn = connection(client_id, false)
|
106
106
|
conn.flush!(true) if conn
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
def disconnect
|
110
110
|
@engine.disconnect if @engine.respond_to?(:disconnect)
|
111
111
|
end
|
112
|
-
|
112
|
+
|
113
113
|
def publish(message)
|
114
114
|
channels = Channel.expand(message['channel'])
|
115
115
|
@engine.publish(message, channels)
|
116
116
|
end
|
117
117
|
end
|
118
|
-
|
118
|
+
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
data/lib/faye/error.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Faye
|
2
2
|
class Error
|
3
|
-
|
3
|
+
|
4
4
|
def self.method_missing(type, *args)
|
5
5
|
code = const_get(type.to_s.upcase)
|
6
6
|
new(code[0], args, code[1]).to_s
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def self.parse(message)
|
10
10
|
message ||= ''
|
11
11
|
return new(nil, [], message) unless Grammar::ERROR =~ message
|
@@ -19,17 +19,17 @@ module Faye
|
|
19
19
|
end
|
20
20
|
|
21
21
|
attr_reader :code, :params, :message
|
22
|
-
|
22
|
+
|
23
23
|
def initialize(code, params, message)
|
24
24
|
@code = code
|
25
25
|
@params = params
|
26
26
|
@message = message
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def to_s
|
30
30
|
"#{ @code }:#{ @params * ',' }:#{ @message }"
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# http://code.google.com/p/cometd/wiki/BayeuxCodes
|
34
34
|
VERSION_MISMATCH = [300, 'Version mismatch']
|
35
35
|
CONNTYPE_MISMATCH = [301, 'Connection types not supported']
|
@@ -43,7 +43,7 @@ module Faye
|
|
43
43
|
EXT_UNKNOWN = [406, 'Unknown extension']
|
44
44
|
PUBLISH_FAILED = [407, 'Failed to publish']
|
45
45
|
SERVER_ERROR = [500, 'Internal server error']
|
46
|
-
|
46
|
+
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
data/lib/faye/mixins/logging.rb
CHANGED
@@ -1,47 +1,47 @@
|
|
1
1
|
module Faye
|
2
2
|
module Logging
|
3
|
-
|
3
|
+
|
4
4
|
DEFAULT_LOG_LEVEL = :error
|
5
|
-
|
5
|
+
|
6
6
|
LOG_LEVELS = {
|
7
7
|
:error => 3,
|
8
8
|
:warn => 2,
|
9
9
|
:info => 1,
|
10
10
|
:debug => 0
|
11
11
|
}
|
12
|
-
|
12
|
+
|
13
13
|
class << self
|
14
14
|
attr_writer :log_level
|
15
|
-
|
15
|
+
|
16
16
|
def log_level
|
17
17
|
@log_level || DEFAULT_LOG_LEVEL
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
attr_writer :log_level
|
22
|
-
|
22
|
+
|
23
23
|
def log_level
|
24
24
|
@log_level || Logging.log_level
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def log(message_args, level)
|
28
28
|
return unless Faye.logger
|
29
29
|
return if LOG_LEVELS[log_level] > LOG_LEVELS[level]
|
30
|
-
|
30
|
+
|
31
31
|
message = message_args.shift.gsub(/\?/) do
|
32
32
|
Faye.to_json(message_args.shift)
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
36
36
|
banner = " [#{ level.to_s.upcase }] [#{ self.class.name }] "
|
37
|
-
|
37
|
+
|
38
38
|
Faye.logger.call(timestamp + banner + message)
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
LOG_LEVELS.each do |level, value|
|
42
42
|
define_method(level) { |*args| log(args, level) }
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -1,30 +1,30 @@
|
|
1
1
|
module Faye
|
2
2
|
module Publisher
|
3
|
-
|
3
|
+
|
4
4
|
def count_listeners(event_type)
|
5
5
|
return 0 unless @subscribers and @subscribers[event_type]
|
6
6
|
@subscribers[event_type].size
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def bind(event_type, &listener)
|
10
10
|
@subscribers ||= {}
|
11
11
|
list = @subscribers[event_type] ||= []
|
12
12
|
list << listener
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def unbind(event_type, &listener)
|
16
16
|
return unless @subscribers and @subscribers[event_type]
|
17
17
|
return @subscribers.delete(event_type) unless listener
|
18
|
-
|
18
|
+
|
19
19
|
@subscribers[event_type].delete_if(&listener.method(:==))
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def trigger(event_type, *args)
|
23
23
|
return unless @subscribers and @subscribers[event_type]
|
24
24
|
listeners = @subscribers[event_type].dup
|
25
25
|
listeners.each { |listener| listener.call(*args) }
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
data/lib/faye/mixins/timeouts.rb
CHANGED
@@ -1,95 +1,95 @@
|
|
1
1
|
module Faye
|
2
2
|
class Channel
|
3
|
-
|
3
|
+
|
4
4
|
include Publisher
|
5
5
|
attr_reader :name
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(name)
|
8
8
|
@name = name
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def <<(message)
|
12
12
|
trigger(:message, message)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def unused?
|
16
16
|
count_listeners(:message).zero?
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
HANDSHAKE = '/meta/handshake'
|
20
20
|
CONNECT = '/meta/connect'
|
21
21
|
SUBSCRIBE = '/meta/subscribe'
|
22
22
|
UNSUBSCRIBE = '/meta/unsubscribe'
|
23
23
|
DISCONNECT = '/meta/disconnect'
|
24
|
-
|
24
|
+
|
25
25
|
META = 'meta'
|
26
26
|
SERVICE = 'service'
|
27
|
-
|
27
|
+
|
28
28
|
class << self
|
29
29
|
def expand(name)
|
30
30
|
segments = parse(name)
|
31
31
|
channels = ['/**', name]
|
32
|
-
|
32
|
+
|
33
33
|
copy = segments.dup
|
34
34
|
copy[copy.size - 1] = '*'
|
35
35
|
channels << unparse(copy)
|
36
|
-
|
36
|
+
|
37
37
|
1.upto(segments.size - 1) do |i|
|
38
38
|
copy = segments[0...i]
|
39
39
|
copy << '**'
|
40
40
|
channels << unparse(copy)
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
channels
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def valid?(name)
|
47
47
|
Grammar::CHANNEL_NAME =~ name or
|
48
48
|
Grammar::CHANNEL_PATTERN =~ name
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def parse(name)
|
52
52
|
return nil unless valid?(name)
|
53
53
|
name.split('/')[1..-1]
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
def unparse(segments)
|
57
57
|
'/' + segments.join('/')
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
def meta?(name)
|
61
61
|
segments = parse(name)
|
62
62
|
segments ? (segments.first == META) : nil
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
def service?(name)
|
66
66
|
segments = parse(name)
|
67
67
|
segments ? (segments.first == SERVICE) : nil
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
def subscribable?(name)
|
71
71
|
return nil unless valid?(name)
|
72
72
|
not meta?(name) and not service?(name)
|
73
73
|
end
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
class Set
|
77
77
|
def initialize(parent = nil, value = nil)
|
78
78
|
@channels = {}
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
def keys
|
82
82
|
@channels.keys
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
def remove(name)
|
86
86
|
@channels.delete(name)
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
def has_subscription?(name)
|
90
90
|
@channels.has_key?(name)
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
def subscribe(names, callback)
|
94
94
|
return unless callback
|
95
95
|
names.each do |name|
|
@@ -97,7 +97,7 @@ module Faye
|
|
97
97
|
channel.bind(:message, &callback)
|
98
98
|
end
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
def unsubscribe(name, callback)
|
102
102
|
channel = @channels[name]
|
103
103
|
return false unless channel
|
@@ -109,7 +109,7 @@ module Faye
|
|
109
109
|
false
|
110
110
|
end
|
111
111
|
end
|
112
|
-
|
112
|
+
|
113
113
|
def distribute_message(message)
|
114
114
|
channels = Channel.expand(message['channel'])
|
115
115
|
channels.each do |name|
|
@@ -118,7 +118,7 @@ module Faye
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
end
|
121
|
-
|
121
|
+
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|