diff --git a/lib/remote_storage/swift.rb b/lib/remote_storage/swift.rb index 98eb1df..6074492 100644 --- a/lib/remote_storage/swift.rb +++ b/lib/remote_storage/swift.rb @@ -289,6 +289,58 @@ module RemoteStorage end def has_name_collision?(user, directory, key) + if settings.use_redis_dir_listing + has_name_collision_via_redis?(user, directory, key) + else + has_name_collision_via_swift?(user, directory, key) + end + end + + def has_name_collision_via_redis?(user, directory, key) + lua_script = <<-EOF + local user = ARGV[1] + local directory = ARGV[2] + local key = ARGV[3] + + -- build table with parent directories from remaining arguments + local parent_dir_count = #ARGV - 3 + local parent_directories = {} + for i = 4, 4 + parent_dir_count do + table.insert(parent_directories, ARGV[i]) + end + + -- check for existing directory with the same name as the document + local redis_key = "rs_meta:"..user..":" + if directory == "" then + redis_key = redis_key..key.."/" + else + redis_key = redis_key..directory.."/"..key.."/" + end + if redis.call("hget", redis_key, "etag") then + return true + end + + for index, dir in pairs(parent_directories) do + if redis.call("hget", "rs_meta:"..user..":"..dir.."/", "etag") then + -- the directory already exists, no need to do further checks + return false + else + -- check for existing document with same name as directory + if redis.call("hget", "rs_meta:"..user..":"..dir, "etag") then + return true + end + end + end + + return false + EOF + + parent_directories = parent_directories_for(directory) + + redis.eval(lua_script, nil, [user, directory, key, *parent_directories]) + end + + def has_name_collision_via_swift?(user, directory, key) # check for existing directory with the same name as the document url = url_for_key(user, directory, key) do_head_request("#{url}/") do |res| diff --git a/spec/swift/app_spec.rb b/spec/swift/app_spec.rb index 9022913..9d20293 100644 --- a/spec/swift/app_spec.rb +++ b/spec/swift/app_spec.rb @@ -64,6 +64,46 @@ describe "App" do root_items = redis.smembers "rs_meta:phil:/:items" root_items.must_equal ["food/"] end + + describe "name collision checks" do + it "is successful when there is no name collision" do + put_stub = OpenStruct.new(headers: {etag: "bla"}) + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + end + + last_response.status.must_equal 200 + + metadata = redis.hgetall "rs_meta:phil:food/aguacate" + metadata["size"].must_equal "2" + end + + it "conflicts when there is a directory with same name as document" do + put_stub = OpenStruct.new(headers: {etag: "bla"}) + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + put "/phil/food", "wontwork" + end + + last_response.status.must_equal 409 + + metadata = redis.hgetall "rs_meta:phil:food" + metadata.must_be_empty + end + + it "conflicts when there is a document with same name as directory" do + put_stub = OpenStruct.new(headers: {etag: "bla"}) + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + put "/phil/food/aguacate/empanado", "wontwork" + end + + last_response.status.must_equal 409 + + metadata = redis.hgetall "rs_meta:phil:food/aguacate/empanado" + metadata.must_be_empty + end + end end end