OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/app/workers/attachments/finish_direct_upload_job.rb

140 lines
4.8 KiB

#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class Attachments::FinishDirectUploadJob < ApplicationJob
queue_with_priority :high
def perform(attachment_id, whitelist: true)
attachment = Attachment.pending_direct_uploads.find_by(id: attachment_id)
# An attachment is guaranteed to have a file.
# But if the attachment is nil the expression attachment&.file will be nil and attachment&.file.local_file
# will throw a NoMethodError: undefined method local_file' for nil:NilClass`.
local_file = attachment && attachment.file.local_file
if local_file.nil?
return Rails.logger.error("File for attachment #{attachment_id} was not uploaded.")
end
User.execute_as(attachment.author) do
attach_uploaded_file(attachment, local_file, whitelist)
end
end
private
def attach_uploaded_file(attachment, local_file, whitelist)
set_attributes_from_file(attachment, local_file)
validate_attachment(attachment, whitelist)
save_attachment(attachment)
journalize_container(attachment)
attachment_created_event(attachment)
rescue StandardError => e
::OpenProject.logger.error e
attachment.destroy
ensure
File.unlink(local_file.path) if File.exist?(local_file.path)
end
def set_attributes_from_file(attachment, local_file)
attachment.extend(OpenProject::ChangedBySystem)
attachment.change_by_system do
attachment.downloads = 0
attachment.set_file_size local_file
attachment.set_content_type local_file
attachment.set_digest local_file
end
end
def save_attachment(attachment)
attachment.save! if attachment.changed?
end
def validate_attachment(attachment, whitelist)
contract = create_contract attachment, whitelist
unless contract.valid?
errors = contract.errors.full_messages.join(", ")
raise "Failed to validate attachment #{attachment.id}: #{errors}"
end
end
def create_contract(attachment, whitelist)
options = derive_contract_options(whitelist)
::Attachments::CreateContract.new attachment,
attachment.author,
options: options
end
def derive_contract_options(whitelist)
case whitelist
when false
{ whitelist: [] }
when Array
{ whitelist: whitelist.map(&:to_s) }
else
{}
end
end
def journalize_container(attachment)
journable = attachment.container
return unless journable&.class&.journaled?
# Touching the journable will lead to the journal created next having its own timestamp.
# That timestamp will not adequately reflect the time the attachment was uploaded. This job
# right here might be executed way later than the time the attachment was uploaded. Ideally,
# the journals would be created bearing the time stamps of the attachment's created_at.
# This remains a TODO.
# But with the timestamp update in place as it is, at least the collapsing of aggregated journals
# from days before with the newly uploaded attachment is prevented.
touch_journable(journable)
Journals::CreateService
.new(journable, attachment.author)
.call
end
def touch_journable(journable)
# Not using touch here on purpose,
# as to avoid changing lock versions on the journables for this change
attributes = journable.send(:timestamp_attributes_for_update_in_model)
timestamps = attributes.index_with { Time.now }
journable.update_columns(timestamps) if timestamps.any?
end
def attachment_created_event(attachment)
OpenProject::Notifications.send(
OpenProject::Events::ATTACHMENT_CREATED,
attachment: attachment
)
end
end