151 行
4.6 KiB
Ruby
151 行
4.6 KiB
Ruby
require 'digest'
|
|
|
|
class MaterialSyncImporter
|
|
Result = Struct.new(:material, :action, :suppressed, :suppression, keyword_init: true)
|
|
|
|
def self.import! attributes
|
|
new(attributes).import!
|
|
end
|
|
|
|
def initialize attributes
|
|
@attributes = attributes
|
|
end
|
|
|
|
def import!
|
|
source = source_attributes
|
|
key = normalized_source_key(source)
|
|
raise ArgumentError, 'normalized_source_key is required for material sync' if key.blank?
|
|
|
|
suppression = suppression_for(source)
|
|
if suppression
|
|
return Result.new(material: nil,
|
|
action: :suppressed,
|
|
suppressed: true,
|
|
suppression:)
|
|
end
|
|
|
|
material =
|
|
Material
|
|
.unscoped
|
|
.find_or_initialize_by(normalized_source_key: key)
|
|
event_type =
|
|
if material.new_record?
|
|
:create
|
|
elsif material.discarded?
|
|
:restore
|
|
else
|
|
:update
|
|
end
|
|
uploaded_blob = uploaded_blob!
|
|
|
|
Material.transaction do
|
|
if material.persisted?
|
|
MaterialVersionRecorder.ensure_snapshot!(material,
|
|
created_by_user: @attributes[:updated_by_user])
|
|
end
|
|
material.assign_attributes(material_attributes.merge(source))
|
|
material.discarded_at = nil if material.respond_to?(:discarded_at=)
|
|
material.file.attach(uploaded_blob) if uploaded_blob
|
|
material.save!
|
|
upsert_export_item!(material)
|
|
MaterialVersionRecorder.record!(material:,
|
|
event_type:,
|
|
created_by_user: @attributes[:updated_by_user])
|
|
end
|
|
MaterialThumbnailGenerator.generate!(material)
|
|
|
|
Result.new(material:,
|
|
action: event_type == :create ? :imported : :updated,
|
|
suppressed: false,
|
|
suppression: nil)
|
|
rescue StandardError
|
|
uploaded_blob&.purge_later
|
|
raise
|
|
ensure
|
|
close_tempfile!
|
|
end
|
|
|
|
private
|
|
|
|
def source_attributes
|
|
{ source_kind: @attributes[:source_kind],
|
|
source_uri: @attributes[:source_uri],
|
|
source_path: @attributes[:source_path],
|
|
source_file_id: @attributes[:source_file_id] }
|
|
end
|
|
|
|
def normalized_source_key source
|
|
MaterialSyncSuppression.normalize_source_key(source_kind: source[:source_kind],
|
|
source_uri: source[:source_uri],
|
|
source_path: source[:source_path],
|
|
source_file_id: source[:source_file_id])
|
|
end
|
|
|
|
def material_attributes
|
|
@attributes.except(:source_kind, :source_uri, :source_path, :source_file_id,
|
|
:file_blob, :file_tempfile, :filename, :content_type,
|
|
:file_sha256, :export_path, :profile)
|
|
end
|
|
|
|
def suppression_for source
|
|
if source[:source_kind] == 'google_drive_file' && source[:source_path].present?
|
|
return MaterialSyncSuppressionMatcher.match_google_drive_candidate(
|
|
drive_file_id: source[:source_file_id],
|
|
relative_path: source[:source_path])
|
|
end
|
|
|
|
MaterialSyncSuppressionMatcher.match(**source)
|
|
end
|
|
|
|
def uploaded_blob!
|
|
return @attributes[:file_blob] if @attributes[:file_blob]
|
|
|
|
tempfile = @attributes[:file_tempfile]
|
|
return nil unless tempfile
|
|
|
|
tempfile.rewind
|
|
blob = ActiveStorage::Blob.create_and_upload!(
|
|
io: tempfile,
|
|
filename: @attributes[:filename],
|
|
content_type: @attributes[:content_type])
|
|
file_sha256 = @attributes[:file_sha256] || Digest::SHA256.file(tempfile.path).hexdigest
|
|
blob.metadata['sha256'] = file_sha256 if file_sha256.present?
|
|
blob.save! if blob.changed?
|
|
tempfile.rewind
|
|
blob
|
|
end
|
|
|
|
def upsert_export_item! material
|
|
export_path = @attributes[:export_path].to_s.strip
|
|
return if export_path.blank?
|
|
|
|
profile = @attributes[:profile].presence || 'legacy_drive'
|
|
path = export_path
|
|
if export_path_taken_by_other?(material, profile, path)
|
|
path = MaterialSyncExportPath.uniquify(path, @attributes[:source_file_id])
|
|
end
|
|
|
|
item = material.material_export_items.find_or_initialize_by(profile:)
|
|
item.export_path = path
|
|
item.enabled = true
|
|
item.created_by_user ||= @attributes[:created_by_user]
|
|
item.save!
|
|
end
|
|
|
|
def export_path_taken_by_other? material, profile, export_path
|
|
MaterialExportItem
|
|
.where(profile:, export_path:)
|
|
.where.not(material_id: material.id)
|
|
.exists?
|
|
end
|
|
|
|
def close_tempfile!
|
|
tempfile = @attributes[:file_tempfile]
|
|
return unless tempfile
|
|
|
|
tempfile.close! unless tempfile.closed?
|
|
rescue StandardError
|
|
nil
|
|
end
|
|
end
|