Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix deduplicated filename not being persisted #2679

Merged
merged 1 commit into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions lib/carrierwave/mounter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,20 +139,22 @@ def remote_urls=(urls)
end

def store!
additions, remains = uploaders.partition(&:cached?)
existing_paths = (@removed_uploaders + remains).map(&:store_path)
additions.each do |uploader|
uploader.deduplicate(existing_paths)
uploader.store!
existing_paths << uploader.store_path
end
@added_uploaders += additions
uploaders.each(&:store!)
@added_uploaders += uploaders.reject(&:staged)
end

def write_identifier
return if record.frozen?

clear! if remove?

additions, remains = uploaders.partition(&:cached?)
existing_identifiers = (@removed_uploaders + remains).map(&:identifier)
additions.each do |uploader|
uploader.deduplicate(existing_identifiers)
existing_identifiers << uploader.identifier
end

record.write_uploader(serialization_column, identifier)
end

Expand Down
12 changes: 6 additions & 6 deletions lib/carrierwave/uploader/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,22 @@ def retrieve_from_store!(identifier)
end

##
# Look for a store path which doesn't collide with the given already-stored paths.
# Look for an identifier which doesn't collide with the given already-stored identifiers.
# It is done by adding a index number as the suffix.
# For example, if there's 'image.jpg' and the @deduplication_index is set to 2,
# The stored file will be named as 'image(2).jpg'.
#
# === Parameters
#
# [current_paths (Array[String])] List of paths for already-stored files
# [current_identifiers (Array[String])] List of identifiers for already-stored files
#
def deduplicate(current_paths)
def deduplicate(current_identifiers)
@deduplication_index = nil
return unless current_paths.include?(store_path)
return unless current_identifiers.include?(identifier)

(1..current_paths.size + 1).each do |i|
(1..current_identifiers.size + 1).each do |i|
@deduplication_index = i
break unless current_paths.include?(store_path)
break unless current_identifiers.include?(identifier)
end
end

Expand Down
1 change: 1 addition & 0 deletions spec/mount_multiple_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ def monkey

it "renames the latter file to avoid filename duplication" do
instance.images = ['bork.txt', File.open(tmp_path('bork.txt'))]
instance.write_images_identifier
instance.store_images!
expect(instance.images.map(&:identifier)).to eq ['bork.txt', 'bork(2).txt']
expect(instance.images[0].read).not_to eq instance.images[1].read
Expand Down
3 changes: 2 additions & 1 deletion spec/mount_single_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def default_url
expect(@instance.image.current_path).to eq(public_path('uploads/test.jpg'))
end

context "when adding a file which has the same filename with the exsting one" do
context "when adding a file which has the same filename with the existing one" do
before { FileUtils.cp(file_path('bork.txt'), tmp_path('test.jpg')) }
after { FileUtils.rm(tmp_path('test.jpg')) }

Expand All @@ -414,6 +414,7 @@ def default_url
old_uploader = @instance.image

@instance.image = File.open(tmp_path('test.jpg'))
@instance.write_image_identifier
@instance.store_image!
expect(@instance.image.identifier).to eq 'test(2).jpg'
expect(old_uploader.read).to eq 'this is stuff'
Expand Down
14 changes: 14 additions & 0 deletions spec/orm/activerecord_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,13 @@ def filename
expect(File.exist?(public_path('uploads/old.jpeg'))).to be_falsey
end

it "should persist the deduplicated filename" do
@event.image = stub_file('old.jpeg')
expect(@event.save).to be_truthy
@event.reload
expect(@event.image.current_path).to eq public_path('uploads/old(2).jpeg')
end

it "should not remove file if validations fail on save" do
Event.validate { |r| r.errors.add :textfile, "FAIL!" }
@event.image = stub_file('new.jpeg')
Expand Down Expand Up @@ -1682,6 +1689,13 @@ def filename
expect(@event.images[0].current_path).to eq public_path('uploads/old(2).jpeg')
end

it "should persist the deduplicated filename" do
@event.images = [stub_file('old.jpeg')]
expect(@event.save).to be_truthy
@event.reload
expect(@event.images[0].current_path).to eq public_path('uploads/old(2).jpeg')
end

it "should not remove file if validations fail on save" do
Event.validate { |r| r.errors.add :textfile, "FAIL!" }
@event.images = [stub_file('new.jpeg')]
Expand Down
10 changes: 5 additions & 5 deletions spec/uploader/store_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -415,23 +415,23 @@ def filename
end

it "tries to find a non-duplicate filename" do
@uploader.deduplicate(['uploads/test.jpg'])
@uploader.deduplicate(['test.jpg'])
expect(@uploader.deduplicated_filename).to eq('test(2).jpg')
end

it "does nothing when filename doesn't collide" do
@uploader.deduplicate(['uploads/file.jpg'])
@uploader.deduplicate(['file.jpg'])
expect(@uploader.deduplicated_filename).to eq('test.jpg')
end

it "chooses the first non-colliding name" do
@uploader.deduplicate(['uploads/test.jpg', 'uploads/test(2).jpg', 'uploads/test(4).jpg'])
@uploader.deduplicate(['test.jpg', 'test(2).jpg', 'test(4).jpg'])
expect(@uploader.deduplicated_filename).to eq('test(3).jpg')
end

it "resets the deduplication index value from the previous attempt" do
@uploader.deduplicate(['uploads/test.jpg'])
@uploader.deduplicate(['uploads/test.png'])
@uploader.deduplicate(['test.jpg'])
@uploader.deduplicate(['test.png'])
expect(@uploader.deduplicated_filename).to eq('test.jpg')
end

Expand Down