Class: Raca::Container

Inherits:
Object
  • Object
show all
Defined in:
lib/raca/container.rb

Overview

Represents a single cloud files container. Contains methods for uploading, downloading, collecting stats, listing files, etc.

You probably don’t want to instantiate this directly, see Raca::Account#containers

Constant Summary collapse

MAX_ITEMS_PER_LIST =
10_000
LARGE_FILE_THRESHOLD =

5 Gb

5_368_709_120
LARGE_FILE_SEGMENT_SIZE =

100 Mb

104_857_600

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(account, region, container_name, opts = {}) ⇒ Container

Returns a new instance of Container.

Raises:

  • (ArgumentError)


20
21
22
23
24
25
26
27
# File 'lib/raca/container.rb', line 20

def initialize(, region, container_name, opts = {})
  raise ArgumentError, "The container name must not contain '/'." if container_name['/']
  @account, @region, @container_name = , region, container_name
  @storage_url = @account.public_endpoint("cloudFiles", region)
  @cdn_url     = @account.public_endpoint("cloudFilesCDN", region)
  @logger = opts[:logger]
  @logger ||= Rails.logger if defined?(Rails)
end

Instance Attribute Details

#container_nameObject (readonly)

Returns the value of attribute container_name.



18
19
20
# File 'lib/raca/container.rb', line 18

def container_name
  @container_name
end

Instance Method Details

#cdn_enable(ttl = 259200) ⇒ Object

use this with caution, it will make EVERY object in the container publicly available via the CDN. CDN enabling can be done via the web UI but only with a TTL of 72 hours. Using the API it’s possible to set a TTL of 50 years.

TTL is defined in seconds, default is 72 hours.



182
183
184
185
186
187
# File 'lib/raca/container.rb', line 182

def cdn_enable(ttl = 259200)
  log "enabling CDN access to #{container_path} with a cache expiry of #{ttl / 60} minutes"

  response = cdn_client.put(container_path, "X-TTL" => ttl.to_i.to_s)
  (200..299).cover?(response.code.to_i)
end

#cdn_metadataObject

Return the key details for CDN access to this container. Can be called on non CDN enabled containers, but the details won’t make much sense.



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/raca/container.rb', line 163

def 
  log "retrieving container CDN metadata from #{container_path}"
  response = cdn_client.head(container_path)
  {
    :cdn_enabled => response["X-CDN-Enabled"] == "True",
    :host => response["X-CDN-URI"],
    :ssl_host => response["X-CDN-SSL-URI"],
    :streaming_host => response["X-CDN-STREAMING-URI"],
    :ttl => response["X-TTL"].to_i,
    :log_retention => response["X-Log-Retention"] == "True"
  }
end

#delete(key) ⇒ Object

Delete key from the container. If the container is on the CDN, the object will still be served from the CDN until the TTL expires.



50
51
52
53
54
55
# File 'lib/raca/container.rb', line 50

def delete(key)
  log "deleting #{key} from #{container_path}"
  object_path = File.join(container_path, Raca::Util.url_encode(key))
  response = storage_client.delete(object_path)
  (200..299).cover?(response.code.to_i)
end

#download(key, filepath) ⇒ Object

Download the object at key into a local file at filepath.

Returns the number of downloaded bytes.



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/raca/container.rb', line 90

def download(key, filepath)
  log "downloading #{key} from #{container_path}"
  object_path = File.join(container_path, Raca::Util.url_encode(key))
  response = storage_client.get(object_path) do |response|
    File.open(filepath, 'wb') do |io|
      response.read_body do |chunk|
        io.write(chunk)
      end
    end
  end
  response["Content-Length"].to_i
end

#expiring_url(object_key, temp_url_key, expires_at = Time.now.to_i + 60) ⇒ Object

Generate a expiring URL for a file that is otherwise private. useful for providing temporary access to files.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/raca/container.rb', line 192

def expiring_url(object_key, temp_url_key, expires_at = Time.now.to_i + 60)
  digest = OpenSSL::Digest::Digest.new('sha1')

  method  = 'GET'
  expires = expires_at.to_i
  path    = File.join(container_path, object_key)
  encoded_path = File.join(container_path, Raca::Util.url_encode(object_key))
  data    = "#{method}\n#{expires}\n#{path}"

  hmac    = OpenSSL::HMAC.new(temp_url_key, digest)
  hmac << data

  "https://#{storage_host}#{encoded_path}?temp_url_sig=#{hmac.hexdigest}&temp_url_expires=#{expires}"
end

#list(options = {}) ⇒ Object

Return an array of files in the container.

Supported options

max - the maximum number of items to return marker - return items alphabetically after this key. Useful for pagination prefix - only return items that start with this string details - return extra details for each file - size, md5, etc



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/raca/container.rb', line 112

def list(options = {})
  max = options.fetch(:max, 100_000_000)
  marker = options.fetch(:marker, nil)
  prefix = options.fetch(:prefix, nil)
  details = options.fetch(:details, nil)
  limit = [max, MAX_ITEMS_PER_LIST].min
  log "retrieving up to #{max} items from #{container_path}"
  request_path = list_request_path(marker, prefix, details, limit)
  result = storage_client.get(request_path).body || ""
  if details
    result = JSON.parse(result)
  else
    result = result.split("\n")
  end
  result.tap {|items|
    if max <= limit
      log "Got #{items.length} items; we don't need any more."
    elsif items.length < limit
      log "Got #{items.length} items; there can't be any more."
    else
      log "Got #{items.length} items; requesting #{limit} more."
      details ? marker = items.last["name"] : marker = items.last
      items.concat list(max: max-items.length, marker: marker, prefix: prefix, details: details)
    end
  }
end

#metadataObject

Return some basic stats on the current container.



151
152
153
154
155
156
157
158
# File 'lib/raca/container.rb', line 151

def 
  log "retrieving container metadata from #{container_path}"
  response = storage_client.head(container_path)
  {
    :objects => response["X-Container-Object-Count"].to_i,
    :bytes => response["X-Container-Bytes-Used"].to_i
  }
end

#object_metadata(key) ⇒ Object

Returns some metadata about a single object in this container.



75
76
77
78
79
80
81
82
83
84
# File 'lib/raca/container.rb', line 75

def (key)
  object_path = File.join(container_path, Raca::Util.url_encode(key))
  log "Requesting metadata from #{object_path}"

  response = storage_client.head(object_path)
  {
    :content_type => response["Content-Type"],
    :bytes => response["Content-Length"].to_i
  }
end

#purge_from_akamai(key, email_address) ⇒ Object

Remove key from the CDN edge nodes on which it is currently cached. The object is not deleted from the container: as the URL is re-requested, the edge cache will be re-filled with the object currently in the container.

This shouldn’t be used except when it’s really required (e.g. when a piece has to be taken down) because it’s expensive: it lodges a support ticket at Akamai. (!)



64
65
66
67
68
69
70
71
# File 'lib/raca/container.rb', line 64

def purge_from_akamai(key, email_address)
  log "Requesting #{File.join(container_path, key)} to be purged from the CDN"
  response = cdn_client.delete(
    File.join(container_path, Raca::Util.url_encode(key)),
    'X-Purge-Email' => email_address
  )
  (200..299).cover?(response.code.to_i)
end

#search(prefix) ⇒ Object

Returns an array of object keys that start with prefix. This is a convenience method that is equivilant to:

container.list(prefix: "foo/bar/")


144
145
146
147
# File 'lib/raca/container.rb', line 144

def search(prefix)
  log "retrieving container listing from #{container_path} items starting with #{prefix}"
  list(prefix: prefix)
end

#upload(key, data_or_path, headers = {}) ⇒ Object

Upload data_or_path (which may be a filename or an IO) to the container, as key.

If headers are provided they will be added to to upload request. Use this to manually specify content type, content disposition, CORS headers, etc.



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/raca/container.rb', line 34

def upload(key, data_or_path, headers = {})
  case data_or_path
  when StringIO, File
    upload_io(key, data_or_path, data_or_path.size, headers)
  when String
    File.open(data_or_path, "rb") do |io|
      upload_io(key, io, io.stat.size, headers)
    end
  else
    raise ArgumentError, "data_or_path must be an IO with data or filename string"
  end
end