lib/duracloud/content.rb in duracloud-client-0.3.0 vs lib/duracloud/content.rb in duracloud-client-0.4.0
- old
+ new
@@ -2,121 +2,96 @@
module Duracloud
#
# A piece of content in DuraCloud
#
- class Content
- include ActiveModel::Model
- include ActiveModel::Dirty
- include Persistence
- include HasProperties
+ class Content < AbstractEntity
+ class CopyError < Error; end
+
CHUNK_SIZE = 1024 * 16
+ COPY_SOURCE_HEADER = "x-dura-meta-copy-source"
+ COPY_SOURCE_STORE_HEADER = "x-dura-meta-copy-source-store"
+ MANIFEST_EXT = ".dura-manifest"
- after_save :changes_applied
-
# Does the content exist in DuraCloud?
- # @return [Boolean] whether the content exists
- # @raise [Duracloud::MessageDigestError] the provided digest in the :md5 attribute
- # does not match the stored value
- def self.exist?(params={})
- find(params) && true
+ # @return [Boolean] whether the content exists.
+ # @raise [Duracloud::MessageDigestError] the provided digest in the :md5 keyword option,
+ # if given, does not match the stored value.
+ def self.exist?(**kwargs)
+ find(**kwargs) && true
rescue NotFoundError
false
end
# Find content in DuraCloud.
# @return [Duraclound::Content] the content
- # @raise [Duracloud::NotFoundError] the space, content, or store does not exist.
- # @raise [Duracloud::MessageDigestError] the provided digest in the :md5 attribute
- # does not match the stored value
- def self.find(params={})
- new(params).tap do |content|
+ # @raise [Duracloud::NotFoundError] the space, content, or store (if given) does not exist.
+ # @raise [Duracloud::MessageDigestError] the provided digest in the :md5 keyword option,
+ # if given, does not match the stored value.
+ def self.find(**kwargs)
+ new(**kwargs).tap do |content|
content.load_properties
end
+ rescue NotFoundError => e
+ ChunkedContent.find(**kwargs)
end
- attr_accessor :space_id, :content_id, :store_id
+ # Create new content in DuraCloud.
+ # @return [Duraclound::Content] the content
+ # @raise [Duracloud::NotFoundError] the space or store (if given) does not exist.
+ # @raise [Duracloud::MessageDigestError] the provided digest in the :md5 keyword option,
+ # if given, does not match the stored value.
+ def self.create(**kwargs)
+ new(**kwargs).save
+ end
+
+ attr_accessor :space_id, :content_id, :store_id,
+ :body, :md5, :content_type
alias_method :id, :content_id
validates_presence_of :space_id, :content_id
- define_attribute_methods :content_type, :body, :md5
-
# Return the space associated with this content.
# @return [Duracloud::Space] the space.
# @raise [Duracloud::NotFoundError] the space or store does not exist.
def space
- Space.find(space_id, store_id)
+ @space ||= Space.find(space_id, store_id)
end
def inspect
"#<#{self.class} space_id=#{space_id.inspect}," \
" content_id=#{content_id.inspect}," \
" store_id=#{store_id || '(default)'}>"
end
- # @api private
- # @raise [Duracloud::NotFoundError] the content does not exist in DuraCloud.
- def load_body
- response = Client.get_content(*args, **query)
- set_md5!(response)
- @body = response.body # don't use setter b/c marks as dirty
- persisted!
- end
-
- def load_properties
- super do |response|
- # don't mark content_type or md5 as changed
- set_md5!(response)
- @content_type = response.content_type
- end
- end
-
- def body=(str_or_io)
- @body = str_or_io
- body_will_change!
- end
-
- # Return the content body, loading from DuraCloud if necessary.
- # @return [String, StringIO] the content body
- def body
- load_body if persisted? && empty?
- @body
- end
-
# Is the content empty?
# @return [Boolean] whether the content is empty (nil or empty string)
def empty?
- @body.nil? || @body.size == 0
+ body.nil? || ( body.respond_to?(:size) && body.size == 0 )
end
- def content_type=(val)
- content_type_will_change! unless val == @content_type
- @content_type = val
+ # Downloads the remote content
+ # @yield [String] chunk of the remote content, if block given.
+ # @return [Duracloud::Response] the response to the content request.
+ # @raise [Duracloud::NotFoundError]
+ def download(&block)
+ Client.get_content(*args, **query, &block)
end
- def content_type
- @content_type
- end
-
- def md5=(val)
- md5_will_change! unless val == @md5
- @md5 = val
- end
-
- def md5
- @md5
- end
-
# @return [Duracloud::Content] the copied content
# The current instance still represents the original content.
- def copy(target_space_id:, target_content_id:, target_store_id: nil)
- copy_headers = {'x-dura-meta-copy-source'=>[space_id, content_id].join('/')}
- copy_headers['x-dura-meta-copy-source-store'] = store_id if store_id
- options = { storeID: target_store_id, headers: copy_headers }
- Client.copy_content(target_space_id, target_content_id, **options)
- Content.find(space_id: target_space_id, content_id: target_content_id, store_id: target_store_id, md5: md5)
+ def copy(**args)
+ dest = args.except(:force)
+ dest[:space_id] ||= space_id
+ dest[:content_id] ||= content_id
+ raise CopyError, "Destination is the same as the source." if dest == copy_source
+ if !args[:force] && Content.exist?(**dest)
+ raise CopyError, "Destination exists and :false option is false."
+ end
+ options = { storeID: dest[:store_id], headers: copy_headers }
+ Client.copy_content(dest[:space_id], dest[:content_id], **options)
+ Content.new(dest.merge(md5: md5))
end
# @return [Duracloud::Content] the moved content
# The current instance still represents the deleted content.
def move(**args)
@@ -125,42 +100,38 @@
copied
end
private
- def set_md5!(response)
- if md5
- if md5 != response.md5
- raise MessageDigestError,
- "Expected MD5 digest (#{md5}) does not match response header: #{response.md5}"
- end
- else
- @md5 = response.md5
- end
- end
-
- def io_like?
- body.respond_to?(:read) && body.respond_to?(:rewind)
- end
-
- def set_properties
- headers = properties.to_h
- headers["Content-Type"] = content_type if content_type_changed?
- options = { headers: headers, query: query }
- Client.set_content_properties(*args, **options)
- end
-
def store
headers = {
"Content-MD5" => md5 || calculate_md5,
"Content-Type" => content_type || "application/octet-stream"
}
headers.merge!(properties)
options = { body: body, headers: headers, query: query }
Client.store_content(*args, **options)
end
+ def copy_headers
+ ch = { COPY_SOURCE_HEADER=>"#{space_id}/#{content_id}" }
+ ch[COPY_SOURCE_STORE_HEADER] = store_id if store_id
+ ch
+ end
+
+ def copy_source
+ { space_id: space_id, content_id: content_id, store_id: store_id }
+ end
+
+ def io_like?
+ body.respond_to?(:read) && body.respond_to?(:rewind)
+ end
+
+ def set_properties
+ Client.set_content_properties(*args, headers: properties, query: query)
+ end
+
def calculate_md5
digest = Digest::MD5.new
if io_like?
body.rewind
while chunk = body.read(CHUNK_SIZE) do
@@ -175,19 +146,28 @@
def properties_class
ContentProperties
end
- def get_properties_response
- Client.get_content_properties(*args, **query)
+ def do_load_properties
+ response = Client.get_content_properties(*args, **query)
+ if md5
+ if md5 != response.md5
+ raise MessageDigestError, "Expected MD5: {#{md5}}; DuraCloud MD5: {#{response.md5}}."
+ end
+ else
+ self.md5 = response.md5
+ end
+ self.properties = response.headers
+ self.content_type = response.content_type
end
def do_delete
Client.delete_content(*args, **query)
end
def do_save
- if !empty? && body_changed?
+ if !empty?
store
elsif persisted?
set_properties
else
raise Error, "Cannot store empty content."