Class: Shrine::Storage::GoogleCloudStorage

Inherits:
Object
  • Object
show all
Defined in:
lib/shrine/storage/google_cloud_storage.rb

Defined Under Namespace

Classes: ProcIO

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project: nil, bucket:, prefix: nil, host: nil, default_acl: nil, object_options: {}, credentials: nil, public: false) ⇒ GoogleCloudStorage

Initialize a Shrine::Storage for GCS allowing for auto-discovery of the Google::Cloud::Storage client.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/shrine/storage/google_cloud_storage.rb', line 13

def initialize(project: nil, bucket:, prefix: nil, host: nil, default_acl: nil, object_options: {}, credentials: nil, public: false)
  @project = project
  @bucket = bucket
  @prefix = prefix
  @host = host
  @object_options = object_options
  @storage = nil
  @credentials = credentials

  @default_acl = if public && default_acl && default_acl != "publicRead"
                   raise Shrine::Error, "You can not set both public and default_acl"
                 elsif public
                   "publicRead"
                 else
                   default_acl
                 end
end

Instance Attribute Details

#bucketObject (readonly)

Returns the value of attribute bucket.



8
9
10
# File 'lib/shrine/storage/google_cloud_storage.rb', line 8

def bucket
  @bucket
end

#hostObject (readonly)

Returns the value of attribute host.



8
9
10
# File 'lib/shrine/storage/google_cloud_storage.rb', line 8

def host
  @host
end

#prefixObject (readonly)

Returns the value of attribute prefix.



8
9
10
# File 'lib/shrine/storage/google_cloud_storage.rb', line 8

def prefix
  @prefix
end

Instance Method Details

#clear!(&block) ⇒ Object

deletes all objects from the storage If block is givem, deletes only objects for which the block evaluates to true.

clear!

or

clear! { |file| file.updated_at < 1.week.ago }



125
126
127
128
129
130
131
132
133
# File 'lib/shrine/storage/google_cloud_storage.rb', line 125

def clear!(&block)
  prefix = "#{@prefix}/" if @prefix
  files = get_bucket.files prefix: prefix

  loop do
    files.each { |file| delete_file(file) if block.nil? || block.call(file) }
    files = files.next or break
  end
end

#delete(id) ⇒ Object

deletes the file from the storage



113
114
115
116
# File 'lib/shrine/storage/google_cloud_storage.rb', line 113

def delete(id)
  file = get_file(id)
  delete_file(file) unless file.nil?
end

#exists?(id) ⇒ Boolean

checks if the file exists on the storage



106
107
108
109
110
# File 'lib/shrine/storage/google_cloud_storage.rb', line 106

def exists?(id)
  file = get_file(id)
  return false if file.nil?
  file.exists?
end

#object_name(id) ⇒ Object



147
148
149
# File 'lib/shrine/storage/google_cloud_storage.rb', line 147

def object_name(id)
  @prefix ? "#{@prefix}/#{id}" : id
end

#open(id, rewindable: true, **options) ⇒ Down::ChunkedIO

Opens the remote file and returns it as Down::ChunkedIO object.

Raises:

  • (Shrine::FileNotFound)

See Also:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/shrine/storage/google_cloud_storage.rb', line 84

def open(id, rewindable: true, **options)
  file = get_file(id)

  raise Shrine::FileNotFound, "file #{id.inspect} not found on storage" unless file

  # create enumerator which lazily yields chunks of downloaded content
  chunks = Enumerator.new do |yielder|
    # trick to get google client to stream the download
    proc_io = ProcIO.new { |data| yielder << data }
    file.download(proc_io, verify: :none, **options)
  end

  # wrap chunks in an IO-like object which downloads when needed
  Down::ChunkedIO.new(
    chunks:     chunks,
    size:       file.size,
    rewindable: rewindable,
    data:       { file: file },
  )
end

#presign(id, **options) ⇒ Object

returns request data (:method, :url, and :headers) for direct uploads



136
137
138
139
140
141
142
143
144
145
# File 'lib/shrine/storage/google_cloud_storage.rb', line 136

def presign(id, **options)
  url = storage.signed_url(@bucket, object_name(id), method: "PUT", **options)

  headers = {}
  headers["Content-Type"] = options[:content_type] if options[:content_type]
  headers["Content-MD5"]  = options[:content_md5] if options[:content_md5]
  headers.merge!(options[:headers]) if options[:headers]

  { method: :put, url: url, headers: headers }
end

#upload(io, id, shrine_metadata: {}, **options) ⇒ Object

If the file is an UploadFile from GCS, issues a copy command, otherwise it uploads a file.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/shrine/storage/google_cloud_storage.rb', line 34

def upload(io, id, shrine_metadata: {}, **options)
  if copyable?(io)
    existing_file = get_bucket(io.storage.bucket).file(io.storage.object_name(io.id))
    file = existing_file.copy(
        @bucket, # dest_bucket_or_path - the bucket to copy the file to
        object_name(id), # dest_path - the path to copy the file to in the given bucket
        acl: options.fetch(:acl) { @default_acl }
    ) do |f|
      # Workaround a bug in File#copy where the content-type is not copied if you provide a block
      # See https://github.com/renchap/shrine-google_cloud_storage/issues/36
      # See https://github.com/googleapis/google-cloud-ruby/issues/4254
      f.content_type = existing_file.content_type

      # update the additional options
      @object_options.merge(options).each_pair do |key, value|
        f.send("#{key}=", value)
      end
    end
    file
  else
    with_file(io) do |file|
      file_options = @object_options.merge(
        content_type: ["mime_type"],
        acl: options.fetch(:acl) { @default_acl }
      ).merge(options)

      get_bucket.create_file(
          file,
          object_name(id), # path
          **file_options
      )
    end
  end
end

#url(id, **options) ⇒ Object

URL to the remote file, accepts options for customizing the URL



70
71
72
73
74
75
76
77
78
79
# File 'lib/shrine/storage/google_cloud_storage.rb', line 70

def url(id, **options)
  if options.key? :expires
    signed_url = storage.signed_url(@bucket, object_name(id), **options)
    signed_url.gsub!(/storage.googleapis.com\/#{@bucket}/, @host) if @host
    signed_url
  else
    host = @host || "storage.googleapis.com/#{@bucket}"
    "https://#{host}/#{Addressable::URI.encode_component(object_name(id), Addressable::URI::CharacterClasses::PATH)}"
  end
end