Skip to content

Commit

Permalink
This gets the 'Legs.connections' method working, which returns an arr…
Browse files Browse the repository at this point in the history
…ay of all the outgoing and incomming legs instances, deprecates Legs.users in favour of Legs.incomming, and if you just want an array of outgoing legs, there's now Legs.outgoing too. This is working correctly as far as I can tell, which means everything about legs is working correctly as far as I know.
  • Loading branch information
Jenna Fox committed Jul 14, 2008
1 parent 12afd10 commit e51248e
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 66 deletions.
34 changes: 13 additions & 21 deletions examples/chat-server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

# this is a work in progress, api's will change and break, one day there will be a functional matching
# client in shoes or something
class User; attr_accessor :id, :name; end

Legs.start do
def initialize
@rooms = Hash.new { {'users' => [], 'messages' => [], 'topic' => 'No topic set'} }
@rooms['Lobby'] = {'topic' => 'General Chit Chat', 'messages' => [], 'users' => []}
end

# returns a list of available rooms
def rooms
room_list = Hash.new
Expand All @@ -20,13 +20,13 @@ def rooms
def join(room_name)
unless @rooms.keys.include?(room_name)
@rooms[room_name.to_s] = @rooms[room_name]
server.broadcast :room_created, room_name
broadcast :room_created, room_name
end

# room = room_object(room_name)
#
# unless room['users'].include?(caller)
# broadcast_to room, 'user_joined', room_name, user_object(caller)
# broadcast room['users'], 'user_joined', room_name, user_object(caller)
# room['users'].push(caller)
# end

Expand All @@ -37,28 +37,29 @@ def join(room_name)
def leave(room_name)
room = @rooms[room_name.to_s]
room['users'].delete(caller)
broadcast_to room, 'user_left', room_name, user_object(caller)
broadcast room['users'], 'user_left', room_name, user_object(caller)
true
end

# sets the room topic message
def set_topic(room, message)
@rooms[room.to_s]['topic'] = message.to_s
broadcast_to room, 'room_changed', room_object(room, :remote, :name, :topic)
def topic=(room, message)
room = @rooms[room.to_s]
room['topic'] = message.to_s
broadcast room['users'], 'room_changed', room_object(room, :remote, :name, :topic)
end

# sets the user's name
def set_name(name)
def name=(name)
caller.meta[:name] = name.to_s
user_rooms(caller).each do |room_name|
broadcast_to room_name, 'user_changed', user_object(caller)
broadcast @rooms[room_name]['users'], 'user_changed', user_object(caller)
end
true
end

# returns information about ones self, clients thusly can find out their user 'id' number
def get_user(object_id = nil)
user = user_object( object_id.nil? ? caller : users.select { |u| u.object_id == object_id.to_i }.first )
def user(object_id = nil)
user = user_object( object_id.nil? ? caller : find_user_by_object_id(object_id) }.first )
user['rooms'] = user_rooms(user)
return user
end
Expand All @@ -68,7 +69,7 @@ def post_message(room_name, message)
room = room_object(room_name)
room['messages'].push(msg = {'user' => user_object(caller), 'time' => Time.now.to_i, 'message' => message.to_s} )
trim_messages room
broadcast_to room, 'message', room_name.to_s, msg
broadcast room['users'], 'message', room_name.to_s, msg
return msg
end

Expand All @@ -82,15 +83,6 @@ def trim_messages room
end
end

# sends a notification to members of a room
def broadcast_to room, *args
room = @rooms[room.to_s] if room.is_a? String
room['users'].each do |user|
user.notify! *args
end
return true
end

# makes a user object suitable for sending back with meta info and stuff
def user_object user
object = {'id' => user.object_id}
Expand Down
110 changes: 71 additions & 39 deletions lib/legs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ class Legs
def initialize(host = 'localhost', port = 30274)
self.class.start(port) if self.class != Legs && !self.class.started?
ObjectSpace.define_finalizer(self) { self.close! }
@socket = TCPSocket.new(host, port) and @parent = false if host.instance_of?(String)
@socket = host and @parent = port if host.instance_of?(TCPSocket)
@responses = Hash.new; @meta = {}; @closed = false
@parent = false; @responses = Hash.new; @meta = {}; @closed = false
@responses_mutex = Mutex.new; @socket_mutex = Mutex.new

if host.instance_of?(TCPSocket)
@socket = host
@parent = port unless port.instance_of?(Numeric)
elsif host.instance_of?(String)
@socket = TCPSocket.new(host, port)
self.class.outgoing_mutex.synchronize { self.class.outgoing.push self }
else
raise "First argument needs to be a hostname, ip, or socket"
end


@handle_data = Proc.new do |data|
data = self.__json_restore(JSON.parse(data))

Expand Down Expand Up @@ -59,14 +68,18 @@ def connected?; @socket.closed? == false and @closed == false; end
def close!
@closed = true
puts "User #{self.inspect} disconnecting" if self.class.log?
@parent.event(:disconnect, self) if @parent

# notify the remote side
notify!('**remote__disconnecting**') rescue nil

@parent.users_mutex.synchronize { @parent.users.delete(self) } if @parent
if @parent
@parent.event(:disconnect, self)
@parent.incomming_mutex.synchronize { @parent.incomming.delete(self) }
else
self.class.outgoing_mutex.synchronize { self.class.outgoing.delete(self) }
end

@socket.close rescue nil
Thread.new { sleep(1); @socket.close rescue nil }
end

# send a notification to this user
Expand Down Expand Up @@ -203,13 +216,14 @@ def get_unique_number; @unique_id ||= 0; @unique_id += 1; end
# the server is started by subclassing Legs, then SubclassName.start
class << Legs
attr_accessor :terminator, :log
attr_reader :users, :server_object, :users_mutex, :messages_mutex
attr_reader :incomming, :outgoing, :server_object, :incomming_mutex, :outgoing_mutex, :messages_mutex
alias_method :log?, :log
alias_method :users, :incomming

def initializer
ObjectSpace.define_finalizer(self) { self.stop! }
@users = []; @messages = Queue.new; @terminator = "\n"; @log = true
@users_mutex = Mutex.new
@incomming = []; @outgoing = []; @messages = Queue.new; @terminator = "\n"; @log = true
@incomming_mutex = Mutex.new; @outgoing_mutex = Mutex.new
end


Expand All @@ -222,7 +236,38 @@ def start(port=30274, &blk)

# make the fake class
@server_class = Class.new
@server_class.module_eval { private; attr_reader :server, :caller; public }
@server_class.module_eval do
private
attr_reader :server, :caller

# sends a notification message to all connected clients
def broadcast *args
if args.first.is_a?(Array)
arr = args.shift; method = args.shift
elsif args.first.is_a?(String) or args.first.is_a?(Symbol)
arr = server.incomming; method = args.shift
else
raise "You need to specify a 'method' to broadcast out to"
end

server.incomming.each { |user| user.notify!(method, *args) }
end

# Finds a user by the value of a certain property... like find_user_by :object_id, 12345
def find_user_by_object_id value
server.incomming.find { |user| user.__object_id == value }
end

# finds user's with the specified meta keys matching the specified values, can use regexps and stuff, like a case block
def find_users_by_meta hash = nil
raise "You need to give find_users_by_meta a hash to check the user's meta hash against" if hash.nil?
server.incomming.select do |user|
hash.all? { |key, value| value === user.meta[key] }
end
end

public # makes it public again for the user code
end
@server_class.module_eval(&blk)
@server_object = @server_class.allocate
@server_object.instance_variable_set(:@server, self)
Expand All @@ -246,7 +291,7 @@ def start(port=30274, &blk)

result = nil

@users_mutex.synchronize do
@incomming_mutex.synchronize do
if methods.include?(method.to_s)
result = @server_object.__send__(method.to_s, *params)
else
Expand All @@ -271,8 +316,8 @@ def start(port=30274, &blk)
@acceptor_thread = Thread.new do
while started?
user = Legs.new(@listener.accept, self)
@users_mutex.synchronize { @users.push user }
puts "User #{user.object_id} connected, number of users: #{@users.length}" if log?
@incomming_mutex.synchronize { @incomming.push user }
puts "User #{user.object_id} connected, number of users: #{@incomming.length}" if log?
self.event :connect, user
end
end
Expand All @@ -282,34 +327,12 @@ def start(port=30274, &blk)
# stops the server, disconnects the clients
def stop
@started = false
@users.each { |user| user.close! }
end

# sends a notification message to all connected clients
def broadcast(method, *args)
@users.each { |user| user.notify!(method, *args) }
end

# Finds a user by the value of a certain property... like find_user_by :object_id, 12345
def find_user_by property, value
@users.find { |user| user.__send(property) == value }
@incomming.each { |user| user.close! }
end

def find_users_by property, *values
@users.select { |user| user.__send(property) == value }
end

# gives you an array of all the instances of Legs which are still connected
# direction can be :both, :in, or :out
def connections direction = :both
return @users if direction == :in
list = Array.new
ObjectSpace.each_object(self) do |leg|
next if list.include?(leg) unless leg.connected?
next unless leg.parent == false if direction == :out
list.push leg
end
return list
# returns an array of all connections
def connections
@incomming + @outgoing
end

# add an event call to the server's message queue
Expand All @@ -333,6 +356,15 @@ def open(*args, &blk)
blk[client]
client.close!
end

# hooks up these methods so you can use them off the main object too!
[:broadcast, :find_user_by_object_id, :find_users_by_meta].each do |name|
define_method name do |*args|
@incomming_mutex.synchronize do
@server_object.__send__(name, *args)
end
end
end
end

Legs.initializer
Expand Down
9 changes: 3 additions & 6 deletions test/tester.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,10 @@ def on_connect
puts m.a == 1 && m.b == 2 && m.c == 3 ?'Success':'Failure'

puts "Testing Legs.connections"
puts (Legs.connections(:out) == i and Legs.connections.length == 2 and Legs.connections(:in) == Legs.users.first) ?'Success':'Failure'
puts ":in => #{Legs.connections(:in).map{ |l| l.inspect }}"
puts ":out => #{Legs.connections(:out).map{ |l| l.inspect }}"
puts ":both => #{Legs.connections.map{ |l| l.inspect }}"
puts (Legs.outgoing.first == i and Legs.connections.length == 2 and Legs.incomming.length == 1) ?'Success':'Failure'

puts "All: "
ObjectSpace.each_object(Legs) { |l| puts " #{l.inspect}" }
puts "Testing the synced wrapping to the find_by... and broadcast methods in the server object"
puts (Legs.find_user_by_object_id(Legs.incomming.first.__object_id) == Legs.incomming.first) ?'Success':'Failure'

puts
puts "Done"

0 comments on commit e51248e

Please sign in to comment.