add DistributedMutex around uploads/optimized_images creation

This commit is contained in:
Régis Hanol 2015-05-12 16:45:33 +02:00
parent 440331e36e
commit 6ae9bcab56
2 changed files with 96 additions and 92 deletions

View file

@ -6,72 +6,74 @@ class OptimizedImage < ActiveRecord::Base
def self.create_for(upload, width, height, opts={})
return unless width > 0 && height > 0
# do we already have that thumbnail?
thumbnail = find_by(upload_id: upload.id, width: width, height: height)
DistributedMutex.synchronize("optimized_image_#{upload.id}_#{width}_#{height}") do
# do we already have that thumbnail?
thumbnail = find_by(upload_id: upload.id, width: width, height: height)
# make sure the previous thumbnail has not failed
if thumbnail && thumbnail.url.blank?
thumbnail.destroy
thumbnail = nil
end
# return the previous thumbnail if any
return thumbnail unless thumbnail.nil?
# create the thumbnail otherwise
external_copy = Discourse.store.download(upload) if Discourse.store.external?
original_path = if Discourse.store.external?
external_copy.try(:path)
else
Discourse.store.path_for(upload)
end
if original_path.blank?
Rails.logger.error("Could not find file in the store located at url: #{upload.url}")
else
# create a temp file with the same extension as the original
extension = File.extname(original_path)
temp_file = Tempfile.new(["discourse-thumbnail", extension])
temp_path = temp_file.path
if extension =~ /\.svg$/i
FileUtils.cp(original_path, temp_path)
resized = true
else
resized = resize(original_path, temp_path, width, height, opts)
# make sure the previous thumbnail has not failed
if thumbnail && thumbnail.url.blank?
thumbnail.destroy
thumbnail = nil
end
if resized
thumbnail = OptimizedImage.create!(
upload_id: upload.id,
sha1: Digest::SHA1.file(temp_path).hexdigest,
extension: extension,
width: width,
height: height,
url: "",
)
# store the optimized image and update its url
url = Discourse.store.store_optimized_image(temp_file, thumbnail)
if url.present?
thumbnail.url = url
thumbnail.save
# return the previous thumbnail if any
return thumbnail unless thumbnail.nil?
# create the thumbnail otherwise
external_copy = Discourse.store.download(upload) if Discourse.store.external?
original_path = if Discourse.store.external?
external_copy.try(:path)
else
Discourse.store.path_for(upload)
end
if original_path.blank?
Rails.logger.error("Could not find file in the store located at url: #{upload.url}")
else
# create a temp file with the same extension as the original
extension = File.extname(original_path)
temp_file = Tempfile.new(["discourse-thumbnail", extension])
temp_path = temp_file.path
if extension =~ /\.svg$/i
FileUtils.cp(original_path, temp_path)
resized = true
else
Rails.logger.error("Failed to store avatar #{size} for #{upload.url} from #{source}")
resized = resize(original_path, temp_path, width, height, opts)
end
else
Rails.logger.error("Failed to create optimized image #{width}x#{height} for #{upload.url}")
if resized
thumbnail = OptimizedImage.create!(
upload_id: upload.id,
sha1: Digest::SHA1.file(temp_path).hexdigest,
extension: extension,
width: width,
height: height,
url: "",
)
# store the optimized image and update its url
url = Discourse.store.store_optimized_image(temp_file, thumbnail)
if url.present?
thumbnail.url = url
thumbnail.save
else
Rails.logger.error("Failed to store avatar #{size} for #{upload.url} from #{source}")
end
else
Rails.logger.error("Failed to create optimized image #{width}x#{height} for #{upload.url}")
end
# close && remove temp file
temp_file.close!
end
# close && remove temp file
temp_file.close!
end
# make sure we remove the cached copy from external stores
if Discourse.store.external?
external_copy.try(:close!) rescue nil
end
# make sure we remove the cached copy from external stores
if Discourse.store.external?
external_copy.try(:close!) rescue nil
thumbnail
end
thumbnail
end
def destroy

View file

@ -52,43 +52,45 @@ class Upload < ActiveRecord::Base
def self.create_for(user_id, file, filename, filesize, options = {})
sha1 = Digest::SHA1.file(file).hexdigest
# do we already have that upload?
upload = find_by(sha1: sha1)
DistributedMutex.synchronize("upload_#{sha1}") do
# do we already have that upload?
upload = find_by(sha1: sha1)
# make sure the previous upload has not failed
if upload && upload.url.blank?
upload.destroy
upload = nil
# make sure the previous upload has not failed
if upload && upload.url.blank?
upload.destroy
upload = nil
end
# return the previous upload if any
return upload unless upload.nil?
# create the upload otherwise
upload = Upload.new
upload.user_id = user_id
upload.original_filename = filename
upload.filesize = filesize
upload.sha1 = sha1
upload.url = ""
upload.origin = options[:origin][0...1000] if options[:origin]
# deal with width & height for images
upload = resize_image(filename, file, upload) if FileHelper.is_image?(filename)
return upload unless upload.save
# store the file and update its url
url = Discourse.store.store_upload(file, upload, options[:content_type])
if url.present?
upload.url = url
upload.save
else
upload.errors.add(:url, I18n.t("upload.store_failure", { upload_id: upload.id, user_id: user_id }))
end
# return the uploaded file
upload
end
# return the previous upload if any
return upload unless upload.nil?
# create the upload otherwise
upload = Upload.new
upload.user_id = user_id
upload.original_filename = filename
upload.filesize = filesize
upload.sha1 = sha1
upload.url = ""
upload.origin = options[:origin][0...1000] if options[:origin]
# deal with width & height for images
upload = resize_image(filename, file, upload) if FileHelper.is_image?(filename)
return upload unless upload.save
# store the file and update its url
url = Discourse.store.store_upload(file, upload, options[:content_type])
if url.present?
upload.url = url
upload.save
else
upload.errors.add(:url, I18n.t("upload.store_failure", { upload_id: upload.id, user_id: user_id }))
end
# return the uploaded file
upload
end
def self.resize_image(filename, file, upload)