lib/whatup/server/server.rb in whatup-0.3.4 vs lib/whatup/server/server.rb in whatup-0.3.5
- old
+ new
@@ -7,22 +7,23 @@
require 'sqlite3'
require 'active_record'
require 'active_support/core_ext/object/blank'
require 'whatup/server/db_init'
+require 'whatup/server/logger'
require 'whatup/server/redirection'
require 'whatup/server/models/client'
require 'whatup/server/models/message'
require 'whatup/server/models/room'
require 'whatup/cli/commands/interactive/interactive'
module Whatup
module Server
class Server # rubocop:disable Metrics/ClassLength
- include Thor::Shell
include DbInit
include Redirection
+ include WhatupLogger
Client = Whatup::Server::Client
attr_reader *%i[ip port address clients pid pid_file rooms]
@@ -47,19 +48,22 @@
# Starts the server.
#
# The server continuously loops, and handle each new client in a separate
# thread.
def start
- say "Starting a server with PID:#{@pid} @ #{@address} ... \n", :green
+ log.info { "Starting a server with PID:#{@pid} @ #{@address} ... \n" }
exit_if_pid_exists!
connect_to_socket!
write_pid!
# Listen for connections, then accept each in a separate thread
loop do
- Thread.new(@socket.accept) { |client| handle_client client }
+ Thread.new(@socket.accept) do |client|
+ log.info { "Accepted new client: #{client.inspect}" }
+ handle_client client
+ end
end
rescue SignalException # In case of ^c
kill
end
@@ -74,83 +78,95 @@
# @param clients [Array<Whatup::Server::Client>] Room's inital clients
# @param name [String] The room's name
#
# @return [Whatup::Server::Room] The created room
def new_room! clients: [], name:
- room = Room.create! name: name, clients: clients
- @rooms << room
- room
+ Room.create!(name: name, clients: clients).tap do |room|
+ @rooms << room
+ end
end
private
# Receives a new client, then continuously gets input from that client
#
# @param client [Whatup::Server::Client] The client
#
- # rubocop:disable Metrics/MethodLength
- def handle_client client
+ def handle_client client # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
client = create_new_client_if_not_existing! client
# Loop forever to maintain the connection
loop do
@clients.reject! &:deleted
- Thread.current.exit if client.deleted
+ if client.deleted
+ log.debug do
+ <<~OUT
+ Client `#{client.name}` has been deleted.
+ #{'Killing'.colorize :red} this thread."
+ OUT
+ end
+ Thread.current.exit
+ end
if client.composing_dm?
handle_dm client
elsif client.chatting?
handle_chatting client
end
# Wait until we get a valid command. This takes as long as the client
# takes.
msg = client.input! unless Whatup::CLI::Interactive.command?(msg)
+ log.info { "#{client.name.colorize :light_blue}> #{msg}" }
- puts "#{client.name}> #{msg}"
-
- begin
- # Send the output to the client
- redirect stdin: client.socket, stdout: client.socket do
+ # Send the output to the client
+ redirect stdin: client.socket, stdout: client.socket do
+ begin
# Invoke the cli using the provided commands and options.
run_thor_command! client: client, msg: msg
+ rescue RuntimeError,
+ ArgumentError,
+ Thor::InvocationError,
+ Thor::UndefinedCommandError => e
+ log.info do
+ "#{client.name.colorize :red}> #{e.class}: #{e.message}"
+ end
+ client.puts case e.class.to_s
+ when 'RuntimeError'
+ 'Invalid input or unknown command'
+ else
+ e.message
+ end
end
- rescue RuntimeError,
- Thor::InvocationError,
- Thor::UndefinedCommandError => e
- puts e.message
- client.puts 'Invalid input or unknown command'
- rescue ArgumentError => e
- puts e.message
- client.puts e.message
end
msg = nil
end
end
- # rubocop:enable Metrics/MethodLength
# Handles inputing direct messages
#
# @param client [Whatup::Server::Client] `client` is the sender of
# the message, and `client.composing_dm` is the recipient.
def handle_dm client
msg = StringIO.new
loop do
input = client.input!
- puts "#{client.name}> #{input}"
+ log.info { "#{client.name.colorize :light_blue}> #{input}" }
msg.puts input
if input == '.exit'
client.puts "Finished dm to `#{client.composing_dm.name}`."
break
end
end
- client.composing_dm
- .received_messages << Message.new(
- sender: client,
- content: msg.string
- )
+ Message.create!(sender: client, content: msg.string).tap do |m|
+ client.composing_dm.received_messages << m
+ log.debug do
+ "Created new message (id = #{m.id}) from `#{client}` to" \
+ "`#{client.composing_dm}`"
+ end
+ end
client.composing_dm = nil
end
# Handles chatting.
#
@@ -161,11 +177,11 @@
input = client.input!
audience = @clients.reject { |c| c.id == client.id }
.select do |c|
client.room.clients.pluck(:id).include? c.id
end
- puts "#{client.name}> #{input}"
+ log.info { "#{client.name.colorize :light_blue}> #{input}" }
if input == '.exit'
client.puts "Exited `#{client.room.name}`."
audience.each { |c| c.puts "#{client.name}> LEFT" }
client.leave_room!
break
@@ -183,35 +199,51 @@
#
# @param client [TCPSocket] The client connection
#
# @return [Whatup::Server::Client] The created client
def create_new_client_if_not_existing! client
- name = client.gets.chomp
+ log.debug { 'Creating new client' }
+
+ name = client.gets&.chomp
+
+ if name.nil?
+ log.debug do
+ 'New client (currently unknown) has left. ' \
+ "#{'Killing'.colorize :red} this thread."
+ end
+ Thread.current.exit
+ end
+
rand_num = SecureRandom.random_number(100).to_s.rjust 3, '0'
name = name == '' ? "ANON-#{rand_num}" : name
if @clients.any? { |c| c.name == name }
client.puts 'That name is taken! Goodbye.'
client.puts 'END'
client.close
+ log.debug do
+ "Existing name `#{name}` entered. " \
+ "#{'Killing'.colorize :red} this thread"
+ end
Thread.current.exit
end
@clients << client = Client.create!(
name: name,
socket: client
)
+ log.info { "Created new client `#{name}` ..." }
- puts "#{client.name} just showed up!"
- client.puts <<~MSG
- Hello, #{client.name}!
+ client.tap do |c|
+ c.puts <<~MSG
+ Hello, #{client.name}!
- Welcome to whatup.
+ Welcome to whatup.
- To get started, type `help`.
- MSG
- client
+ To get started, type `help`.
+ MSG
+ end
end
# Initialize a new cli class using the initial command and options,
# and then set any instance variables, since Thor will create a new
# class instance when it's invoked.
@@ -231,42 +263,52 @@
cli.invoke cli.args.first, cli.args.drop(1)
end
# Kills the server if a PID for this app exists
def exit_if_pid_exists!
+ log.debug { "Checking if `#{@pid}` exists ..." }
+
return unless running?
- say <<~EXIT, :cyan
+ log.info <<~EXIT
A server appears to already be running!
Check `#{@pid_file}`.
EXIT
kill
end
# Connect a new socket for this server to start listening on the specified
# address and port.
def connect_to_socket!
+ log.info do
+ "#{'Opening'.colorize :blue} TCP socket at `#{@ip}:#{@port}`"
+ end
@socket = TCPServer.open @ip, @port
rescue Errno::EADDRINUSE
- puts 'Address already in use!'
+ log.error "Address `#{@ip}:#{@port}` is already in use!"
kill
end
# Write this process's PID to the PID file
def write_pid!
+ log.debug { "Writing PID to `#{@pid}`" }
File.open(@pid_file, 'w') { |f| f.puts Process.pid }
end
# @return [Bool] Whether or not a PID for this app exists
def running?
File.file? @pid_file
end
# Kills the server and removes the PID file
def kill
- say "Killing the server with PID:#{Process.pid} ...", :red
+ log.info do
+ "#{'Killing'.colorize :red} the server with " \
+ "PID:#{Process.pid} ..."
+ end
FileUtils.rm_rf @pid_file
+ log.debug { "Removed `#{@pid_file}`." }
exit
end
end
end
end