lib/lita/handlers/totems.rb in lita-totems-0.1.0 vs lib/lita/handlers/totems.rb in lita-totems-0.2.0

- old
+ new

@@ -1,8 +1,7 @@ -require "lita" -require 'active_support/core_ext/integer/inflections' -require 'active_support/core_ext/object/blank' +require 'lita' +require 'chronic_duration' require 'redis-semaphore' module Lita module Handlers class Totems < Handler @@ -54,24 +53,25 @@ (\s+info? (\s+(?<totem>\w+))? )? $ }x, - :info, - help: { - 'totems info' => "Shows info of all totems queues", - 'totems info TOTEM' => 'Shows info of just one totem' - }) + :info, + help: { + 'totems info' => "Shows info of all totems queues", + 'totems info TOTEM' => 'Shows info of just one totem' + }) def destroy(response) totem = response.match_data[:totem] if redis.exists("totem/#{totem}") redis.del("totem/#{totem}") redis.del("totem/#{totem}/list") redis.srem("totems", totem) owning_user_id = redis.get("totem/#{totem}/owning_user_id") redis.srem("user/#{owning_user_id}/totems", totem) if owning_user_id + redis.del("totem/#{totem}/waiting_since") response.reply(%{Destroyed totem "#{totem}".}) else response.reply(%{Error: totem "#{totem}" doesn't exist.}) end end @@ -96,49 +96,69 @@ return end user_id = response.user.id + if queued_by_user(user_id).include?(totem) + response.reply %{Error: you are already in the queue for "#{totem}".} + return + end + + if redis.smembers("user/#{user_id}/totems").include?(totem) + response.reply %{Error: you already have the totem "#{totem}".} + return + end + token_acquired = false queue_size = nil Redis::Semaphore.new("totem/#{totem}", redis: redis).lock do - if redis.llen("totem/#{totem}/list") == 0 && redis.get("totem/#{totem}/owning_user_id").blank? + if redis.llen("totem/#{totem}/list") == 0 && redis.get("totem/#{totem}/owning_user_id").nil? # take it: token_acquired = true redis.set("totem/#{totem}/owning_user_id", user_id) + redis.hset("totem/#{totem}/waiting_since", user_id, Time.now.to_i) else # queue: - queue_size = redis.lpush("totem/#{totem}/list", user_id) + queue_size = redis.rpush("totem/#{totem}/list", user_id) + redis.hset("totem/#{totem}/waiting_since", user_id, Time.now.to_i) end end if token_acquired + # TODO don't readd to totems you are already waiting for! redis.sadd("user/#{user_id}/totems", totem) response.reply(%{#{response.user.name}, you now have totem "#{totem}".}) else - response.reply(%{#{response.user.name}, you are #{queue_size.ordinalize} in line for totem "#{totem}".}) + response.reply(%{#{response.user.name}, you are \##{queue_size} in line for totem "#{totem}".}) end end def yield(response) - user_id = response.user.id - totems_owned_by_user = redis.smembers("user/#{user_id}/totems") - if totems_owned_by_user.empty? + user_id = response.user.id + totems_owned_by_user = redis.smembers("user/#{user_id}/totems") + totems_queued_by_user = queued_by_user(user_id) + if totems_owned_by_user.empty? && totems_queued_by_user.empty? response.reply "Error: You do not have any totems to yield." - elsif totems_owned_by_user.size == 1 + elsif totems_owned_by_user.size == 1 && !response.match_data[:totem] && totems_queued_by_user.empty? yield_totem(totems_owned_by_user[0], user_id, response) - else # totems count > 1 + else totem_specified = response.match_data[:totem] + # if they don't specify and are only queued for a single totem, yield that one + totem_specified = totems_queued_by_user.first if !totem_specified && totems_queued_by_user.size == 1 && totems_owned_by_user.empty? if totem_specified if totems_owned_by_user.include?(totem_specified) yield_totem(totem_specified, user_id, response) + elsif totems_queued_by_user.include?(totem_specified) + redis.lrem("totem/#{totem_specified}/list", 0, user_id) + redis.hdel("totem/#{totem_specified}/waiting_since", user_id) + response.reply("You are no longer in line for the \"#{totem_specified}\" totem.") else - response.reply %{Error: You don't own the "#{totem_specified}" totem.} + response.reply %{Error: You don't own and aren't waiting for the "#{totem_specified}" totem.} end else - response.reply "You must specify a totem to yield. Totems you own: #{totems_owned_by_user.sort}" + response.reply "You must specify a totem to yield. Totems you own: #{totems_owned_by_user.sort}. Totems you are in line for: #{totems_queued_by_user.sort}." end end end def kick(response) @@ -153,60 +173,88 @@ response.reply %{Error: Nobody owns totem "#{totem}" so you can't kick someone from it.} return end redis.srem("user/#{past_owning_user_id}/totems", totem) - robot.send_messages(User.new(past_owning_user_id), %{You have been kicked from totem "#{totem}".}) + redis.hdel("totem/#{totem}/waiting_since", past_owning_user_id) + robot.send_messages(Lita::Source.new(user: Lita::User.find_by_id(past_owning_user_id)), %{You have been kicked from totem "#{totem}".}) next_user_id = redis.lpop("totem/#{totem}/list") - redis.set("totem/#{totem}/owning_user_id", next_user_id) if next_user_id + redis.set("totem/#{totem}/owning_user_id", next_user_id) redis.sadd("user/#{next_user_id}/totems", totem) - robot.send_messages(User.new(next_user_id), %{You are now in possession of totem "#{totem}".}) + redis.hset("totem/#{totem}/waiting_since", next_user_id, Time.now.to_i) + robot.send_messages(Lita::Source.new(user: Lita::User.find_by_id(next_user_id)), %{You are now in possession of totem "#{totem}".}) + else + redis.del("totem/#{totem}/owning_user_id") end end def info(response) totem_param = response.match_data[:totem] - resp = if totem_param.present? - list_users_print(totem_param) - else - r = "Totems:\n" - redis.smembers("totems").each do |totem| - r += "- #{totem}\n" - r += list_users_print(totem, ' ') - end - r - end + resp = unless totem_param.nil? || totem_param.empty? + list_users_print(totem_param) + else + users_cache = new_users_cache + r = "Totems:\n" + redis.smembers("totems").each do |totem| + r += "- #{totem}\n" + r += list_users_print(totem, ' ', users_cache) + end + r + end response.reply resp end private - def list_users_print(totem, prefix='') + def new_users_cache + Hash.new { |h, id| h[id] = Lita::User.find_by_id(id) } + end + + def list_users_print(totem, prefix='', users_cache=new_users_cache) str = '' first_id = redis.get("totem/#{totem}/owning_user_id") if first_id - str += "#{prefix}1. User id #{first_id}\n" + waiting_since_hash = redis.hgetall("totem/#{totem}/waiting_since") + str += "#{prefix}1. #{users_cache[first_id].name} (held for #{waiting_duration(waiting_since_hash[first_id])})\n" rest = redis.lrange("totem/#{totem}/list", 0, -1) rest.each_with_index do |user_id, index| - str += "#{prefix}#{index+2}. User id #{user_id}\n" + str += "#{prefix}#{index+2}. #{users_cache[user_id].name} (waiting for #{waiting_duration(waiting_since_hash[user_id])})\n" end end str end + def waiting_duration(time) + ChronicDuration.output(Time.now.to_i - time.to_i, format: :short) || "0s" + end + def yield_totem(totem, user_id, response) redis.srem("user/#{user_id}/totems", totem) + redis.hdel("totem/#{totem}/waiting_since", user_id) next_user_id = redis.lpop("totem/#{totem}/list") if next_user_id + redis.set("totem/#{totem}/owning_user_id", next_user_id) redis.sadd("user/#{next_user_id}/totems", totem) - robot.send_messages(User.new(next_user_id), %{You are now in possession of totem "#{totem}."}) - response.reply "You have yielded the totem to #{next_user_id}." + redis.hset("totem/#{totem}/waiting_since", next_user_id, Time.now.to_i) + next_user = Lita::User.find_by_id(next_user_id) + robot.send_messages(Lita::Source.new(user: next_user), %{You are now in possession of totem "#{totem}."}) + response.reply "You have yielded the totem to #{next_user.name}." else + redis.del("totem/#{totem}/owning_user_id") response.reply %{You have yielded the "#{totem}" totem.} end - redis.set("totem/#{totem}/owning_user_id", next_user_id) end + + def queued_by_user(user_id) + redis.smembers("totems").select do |totem| + # there's no easy way to check membership in a list in redis + # right now let's iterate through the list, but to make this + # more performant we could convert these lists to sorted sets + redis.lrange("totem/#{totem}/list", 0, -1).include?(user_id) + end + end + end Lita.register_handler(Totems) end end