lib/locker/label.rb in lita-locker-0.7.0 vs lib/locker/label.rb in lita-locker-1.0.0

- old
+ new

@@ -1,67 +1,174 @@ # Locker subsystem module Locker # Label helpers module Label - def label(name) - redis.hgetall("label_#{name}") - end + # Proper Resource class + class Label + include Redis::Objects - def labels - redis.keys('label_*') - end + value :state + value :owner_id + value :taken_at - def label_exists?(name) - redis.exists("label_#{name}") - end + set :membership + list :wait_queue + list :journal - def lock_label!(name, owner, time_until) - return false unless label_exists?(name) - key = "label_#{name}" - members = label_membership(name) - members.each do |m| - return false unless lock_resource!(m, owner, time_until) + lock :coord, expiration: 5 + + attr_reader :id + + def initialize(key) + fail 'Unknown label key' unless Label.exists?(key) + @id = Label.normalize(key) end - redis.hset(key, 'state', 'locked') - redis.hset(key, 'owner_id', owner.id) - redis.hset(key, 'until', time_until) - true - end - def unlock_label!(name) - return false unless label_exists?(name) - key = "label_#{name}" - members = label_membership(name) - members.each do |m| - unlock_resource!(m) + def self.exists?(key) + redis.sismember('label-list', Label.normalize(key)) end - redis.hset(key, 'state', 'unlocked') - redis.hset(key, 'owner_id', '') - true - end - def create_label(name) - label_key = "label_#{name}" - redis.hset(label_key, 'state', 'unlocked') unless - resource_exists?(name) || label_exists?(name) - end + def self.create(key) + fail 'Label key already exists' if Label.exists?(key) + redis.sadd('label-list', Label.normalize(key)) + l = Label.new(key) + l.state = 'unlocked' + l.owner_id = '' + l.log('Created') + l + end - def delete_label(name) - label_key = "label_#{name}" - redis.del(label_key) if label_exists?(name) - end + def self.delete(key) + fail 'Unknown label key' unless Label.exists?(key) + %w(state, owner_id, membership, wait_queue, journal).each do |item| + redis.del("label:#{key}:#{item}") + end + redis.srem('label-list', Label.normalize(key)) + end - def label_membership(name) - redis.smembers("membership_#{name}") + def self.list + redis.smembers('label-list').sort + end + + def self.normalize(key) + key.strip.downcase + end + + def lock!(owner_id) + if locked? + wait_queue << owner_id if wait_queue.last != owner_id + return false + end + + coord_lock.lock do + membership.each do |resource_name| + r = Locker::Resource::Resource.new(resource_name) + return false if r.locked? + end + # TODO: read-modify-write cycle, not the best + membership.each do |resource_name| + r = Locker::Resource::Resource.new(resource_name) + r.lock!(owner_id) + end + self.owner_id = owner_id + self.state = 'locked' + self.taken_at = Time.now.utc + end + u = Lita::User.fuzzy_find(owner_id) + log("Locked by #{u.name}") + true + end + + def unlock! + return true if state == 'unlocked' + coord_lock.lock do + self.owner_id = '' + self.state = 'unlocked' + self.taken_at = '' + membership.each do |resource_name| + r = Locker::Resource::Resource.new(resource_name) + r.unlock! + end + end + log('Unlocked') + + # FIXME: Possible race condition where resources become unavailable between unlock and relock + if wait_queue.count > 0 + next_user = wait_queue.shift + self.lock!(next_user) + end + true + end + + def steal!(owner_id) + log("Stolen from #{owner.id} to #{owner_id}") + wait_queue.unshift(owner_id) + self.unlock! + end + + def locked? + (state == 'locked') + end + + def add_resource(resource) + log("Resource #{resource.id} added") + resource.labels << id + membership << resource.id + end + + def remove_resource(resource) + log("Resource #{resource.id} removed") + resource.labels.delete(id) + membership.delete(resource.id) + end + + def owner + return nil unless locked? + Lita::User.find_by_id(owner_id.value) + end + + def held_for + return '' unless locked? + TimeLord::Time.new(Time.parse(taken_at.value) - 1).period.to_words + end + + def to_json + val = { id: id, + state: state.value, + membership: membership } + + if locked? + val[:owner_id] = owner_id.value + val[:taken_at] = taken_at.value + val[:wait_queue] = wait_queue + end + + val.to_json + end + + def log(statement) + journal << "#{Time.now.utc}: #{statement}" + end end - def add_resource_to_label(label, resource) - return unless label_exists?(label) && resource_exists?(resource) - redis.sadd("membership_#{label}", resource) + def label_ownership(name) + l = Label.new(name) + return label_dependencies(name) unless l.locked? + mention = l.owner.mention_name ? "(@#{l.owner.mention_name})" : '' + failed(t('label.owned_lock', name: name, owner_name: l.owner.name, mention: mention, time: l.held_for)) end - def remove_resource_from_label(label, resource) - return unless label_exists?(label) && resource_exists?(resource) - redis.srem("membership_#{label}", resource) + def label_dependencies(name) + msg = failed(t('label.dependency')) + "\n" + deps = [] + l = Label.new(name) + l.membership.each do |resource_name| + resource = Locker::Resource::Resource.new(resource_name) + if resource.state.value == 'locked' + deps.push "#{resource_name} - #{resource.owner.name}" + end + end + msg += deps.join("\n") + msg end end end