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