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.

Parameters:

  • project (String) (defaults to: nil)

    Provide if not using auto discovery

See Also:



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

Returns:

  • (Boolean)


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.

Returns:

  • (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.

Parameters:

  • io (IO)
    • io like object

  • id (String)
    • location



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