# frozen_string_literal: truerequire"aws-sdk-s3"require"active_support/core_ext/numeric/bytes"moduleActiveStorage# Wraps the Amazon Simple Storage Service (S3) as an Active Storage service.# See ActiveStorage::Service for the generic API documentation that applies to all services.classService::S3Service<Serviceattr_reader:client,:bucket,:upload_optionsdefinitialize(bucket:,upload: {},**options)@client=Aws::S3::Resource.new(**options)@bucket=@client.bucket(bucket)@upload_options=uploadenddefupload(key,io,checksum: nil,content_type: nil,**)instrument:upload,key: key,checksum: checksumdoobject_for(key).put(upload_options.merge(body: io,content_md5: checksum,content_type: content_type))rescueAws::S3::Errors::BadDigestraiseActiveStorage::IntegrityErrorendenddefdownload(key,&block)ifblock_given?instrument:streaming_download,key: keydostream(key,&block)endelseinstrument:download,key: keydoobject_for(key).get.body.string.force_encoding(Encoding::BINARY)rescueAws::S3::Errors::NoSuchKeyraiseActiveStorage::FileNotFoundErrorendendenddefdownload_chunk(key,range)instrument:download_chunk,key: key,range: rangedoobject_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end??range.end-1:range.end}").body.read.force_encoding(Encoding::BINARY)rescueAws::S3::Errors::NoSuchKeyraiseActiveStorage::FileNotFoundErrorendenddefdelete(key)instrument:delete,key: keydoobject_for(key).deleteendenddefdelete_prefixed(prefix)instrument:delete_prefixed,prefix: prefixdobucket.objects(prefix: prefix).batch_delete!endenddefexist?(key)instrument:exist,key: keydo|payload|answer=object_for(key).exists?payload[:exist]=answeranswerendenddefurl(key,expires_in:,filename:,disposition:,content_type:)instrument:url,key: keydo|payload|generated_url=object_for(key).presigned_url:get,expires_in: expires_in.to_i,response_content_disposition: content_disposition_with(type: disposition,filename: filename),response_content_type: content_typepayload[:url]=generated_urlgenerated_urlendenddefurl_for_direct_upload(key,expires_in:,content_type:,content_length:,checksum:)instrument:url,key: keydo|payload|generated_url=object_for(key).presigned_url:put,expires_in: expires_in.to_i,content_type: content_type,content_length: content_length,content_md5: checksumpayload[:url]=generated_urlgenerated_urlendenddefheaders_for_direct_upload(key,content_type:,checksum:,**){"Content-Type"=>content_type,"Content-MD5"=>checksum}endprivatedefobject_for(key)bucket.object(key)end# Reads the object for the given key in chunks, yielding each to the block.defstream(key)object=object_for(key)chunk_size=5.megabytesoffset=0raiseActiveStorage::FileNotFoundErrorunlessobject.exists?whileoffset<object.content_lengthyieldobject.get(range: "bytes=#{offset}-#{offset+chunk_size-1}").body.read.force_encoding(Encoding::BINARY)offset+=chunk_sizeendendendend