Class: Rubydora::Datastream

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Callbacks
Includes:
ActiveModel::Dirty
Defined in:
lib/rubydora/datastream.rb

Overview

This class represents a Fedora datastream object and provides helper methods for creating and manipulating them.

Constant Summary collapse

DS_ATTRIBUTES =

mapping datastream attributes (and api parameters) to datastream profile names

{:controlGroup => :dsControlGroup, :dsLocation => :dsLocation, :altIDs => nil, :dsLabel => :dsLabel, :versionable => :dsVersionable, :dsState => :dsState, :formatURI => :dsFormatURI, :checksumType => :dsChecksumType, :checksum => :dsChecksum, :mimeType => :dsMIME, :logMessage => nil, :ignoreContent => nil, :lastModifiedDate => nil, :content => nil, :asOfDateTime => nil}
DS_DEFAULT_ATTRIBUTES =
{ :controlGroup => 'M', :dsState => 'A', :versionable => true }
DS_READONLY_ATTRIBUTES =
[ :dsCreateDate , :dsSize, :dsVersionID ]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(digital_object, dsid, options = {}, default_instance_attributes = {}) ⇒ Datastream

Initialize a Rubydora::Datastream object, which may or may not already exist in the datastore.

Provides ‘after_initialize` callback for extensions

Parameters:

  • (Rubydora::DigitalObject)
  • Datastream (String)

    ID

  • default (Hash)

    attribute values (used esp. for creating new datastreams)



100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rubydora/datastream.rb', line 100

def initialize(digital_object, dsid, options = {}, default_instance_attributes = {})
  run_callbacks :initialize do
    @digital_object = digital_object
    @dsid = dsid
    @options = options
    @default_attributes = default_attributes.merge(default_instance_attributes)
    options.each do |key, value|
      self.send(:"#{key}=", value)
    end
  end
end

Instance Attribute Details

#digital_objectObject (readonly)

Returns the value of attribute digital_object.



16
17
18
# File 'lib/rubydora/datastream.rb', line 16

def digital_object
  @digital_object
end

#dsidObject (readonly)

Returns the value of attribute dsid.



16
17
18
# File 'lib/rubydora/datastream.rb', line 16

def dsid
  @dsid
end

Class Method Details

.default_attributesObject



79
80
81
# File 'lib/rubydora/datastream.rb', line 79

def self.default_attributes
  DS_DEFAULT_ATTRIBUTES
end

Instance Method Details

#asOfDateTime(asOfDateTime = nil) ⇒ Object



71
72
73
74
75
76
77
# File 'lib/rubydora/datastream.rb', line 71

def asOfDateTime(asOfDateTime = nil)
  if asOfDateTime.nil?
    return @asOfDateTime
  end

  self.class.new(@digital_object, dsid, @options.merge(:asOfDateTime => asOfDateTime))
end

#changed?Boolean

Returns:

  • (Boolean)


201
202
203
# File 'lib/rubydora/datastream.rb', line 201

def changed?
  super || content_changed?
end

#contentObject Also known as: read

This method is overridden in ActiveFedora, so we didn’t



127
128
129
# File 'lib/rubydora/datastream.rb', line 127

def content
  local_or_remote_content(true)
end

#content=(new_content) ⇒ String or IO

Set the content of the datastream

Parameters:

  • (String or IO)

Returns:

  • (String or IO)


175
176
177
178
# File 'lib/rubydora/datastream.rb', line 175

def content=(new_content)
  raise "Can't change values on older versions" if @asOfDateTime
  @content = new_content
end

#content_changed?Boolean

Returns:

  • (Boolean)


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/rubydora/datastream.rb', line 180

def content_changed?
  return false if ['E','R'].include? controlGroup
  return false unless content_loaded?
  return true if new? && !local_or_remote_content(false).blank? # new datastreams must have content

  if controlGroup == "X"
    if self.eager_load_datastream_content
      return !EquivalentXml.equivalent?(Nokogiri::XML(content), Nokogiri::XML(datastream_content))
    else
      return !EquivalentXml.equivalent?(Nokogiri::XML(content), Nokogiri::XML(@datastream_content))
    end
  else
    if self.eager_load_datastream_content
      return local_or_remote_content(false) != datastream_content
    else
      return local_or_remote_content(false) != @datastream_content
    end
  end
  super
end

#content_loaded?Boolean

Returns:

  • (Boolean)


219
220
221
# File 'lib/rubydora/datastream.rb', line 219

def content_loaded?
  !@content.nil?
end

#createRubydora::Datastream

Add datastream to Fedora



299
300
301
302
303
304
305
306
307
# File 'lib/rubydora/datastream.rb', line 299

def create
  check_if_read_only
  run_callbacks :create do
    p = repository.add_datastream( to_api_params.merge({ :pid => pid, :dsid => dsid, :content => content })) || {}
    reset_profile_attributes
    self.profile= p unless p.empty?
    self.class.new(digital_object, dsid, @options)
  end
end

#current_version?Boolean

Returns:

  • (Boolean)


291
292
293
294
295
# File 'lib/rubydora/datastream.rb', line 291

def current_version?
  return true if new?
  vers = versions
  vers.empty? || dsVersionID == vers.first.dsVersionID
end

#datastream_contentObject



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rubydora/datastream.rb', line 152

def datastream_content
  return nil if new?

  @datastream_content ||=begin
    options = { :pid => pid, :dsid => dsid }
    options[:asOfDateTime] = asOfDateTime if asOfDateTime

    repository.datastream_dissemination options
  rescue RestClient::ResourceNotFound
  end
end

#datastream_will_change!Object



338
339
340
# File 'lib/rubydora/datastream.rb', line 338

def datastream_will_change!
  attribute_will_change! :profile
end

#default_attributesObject



83
84
85
# File 'lib/rubydora/datastream.rb', line 83

def default_attributes
  @default_attributes ||= self.class.default_attributes
end

#default_attributes=(attributes) ⇒ Object



87
88
89
# File 'lib/rubydora/datastream.rb', line 87

def default_attributes=(attributes)
  @default_attributes = default_attributes.merge attributes
end

#deleteRubydora::Datastream

Purge the datastream from Fedora

Returns:



328
329
330
331
332
333
334
335
336
# File 'lib/rubydora/datastream.rb', line 328

def delete
  check_if_read_only
  run_callbacks :destroy do
    repository.purge_datastream(:pid => pid, :dsid => dsid) unless self.new?
    digital_object.datastreams.delete(dsid)
    reset_profile_attributes
    self
  end
end

#empty?Boolean

Returns:

  • (Boolean)


223
224
225
# File 'lib/rubydora/datastream.rb', line 223

def empty?
  !has_content?
end

#external?boolean

Returns is this an external datastream?.

Returns:

  • (boolean)

    is this an external datastream?



343
344
345
# File 'lib/rubydora/datastream.rb', line 343

def external?
  controlGroup == 'E'
end

#has_content?Boolean

Returns:

  • (Boolean)


205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/rubydora/datastream.rb', line 205

def has_content?
  # persisted objects are required to have content
  return true unless new?

  # type E and R have content if dsLocation is set.
  return dsLocation.present? if ['E','R'].include? controlGroup

  # type M has content if dsLocation is not empty
  return true if controlGroup == 'M' && dsLocation.present?

  # if we've set content, then we have content
  behaves_like_io?(@content) || content.present?
end

#inline?boolean

Returns is this an inline datastream?.

Returns:

  • (boolean)

    is this an inline datastream?



358
359
360
# File 'lib/rubydora/datastream.rb', line 358

def inline?
  controlGroup == 'X'
end

#local_or_remote_content(ensure_fetch = true) ⇒ String

Retrieve the content of the datastream (and cache it)

Parameters:

  • ensure_fetch (Boolean) (defaults to: true)

    <true> if true, it will grab the content from the repository if is not already loaded

Returns:

  • (String)


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/rubydora/datastream.rb', line 134

def local_or_remote_content(ensure_fetch = true)
  return @content if new?

  @content ||= ensure_fetch ? datastream_content : @datastream_content

  if behaves_like_io?(@content)
    begin
      @content.rewind
      @content.read
    ensure
      @content.rewind
    end
  else
    @content
  end
end

#managed?boolean

Returns is this a managed datastream?.

Returns:

  • (boolean)

    is this a managed datastream?



353
354
355
# File 'lib/rubydora/datastream.rb', line 353

def managed?
  controlGroup == 'M'
end

#new?Boolean

Does this datastream already exist?

Returns:

  • (Boolean)


119
120
121
122
123
124
# File 'lib/rubydora/datastream.rb', line 119

def new?
  digital_object.nil? ||
    (digital_object.respond_to?(:new_record?) && digital_object.new_record?) ||
    (digital_object.respond_to?(:new?) && digital_object.new?) ||
    profile.empty?
end

#pidObject

Helper method to get digital object pid



113
114
115
# File 'lib/rubydora/datastream.rb', line 113

def pid
  digital_object.pid
end

#profile(opts = {}) ⇒ Hash

Retrieve the datastream profile as a hash (and cache it)

Parameters:

  • opts (Hash) (defaults to: {})

    :validateChecksum if you want fedora to validate the checksum

Returns:

  • (Hash)

    see Fedora #getDatastream documentation for keys



268
269
270
271
272
273
274
275
276
277
# File 'lib/rubydora/datastream.rb', line 268

def profile(opts= {})
  if @profile && !(opts[:validateChecksum] && !@profile.has_key?('dsChecksumValid'))
    ## Force a recheck of the profile if they've passed :validateChecksum and we don't have dsChecksumValid
    return @profile
  end

  return @profile = {} unless digital_object.respond_to? :repository

  @profile = repository.datastream_profile(pid, dsid, opts[:validateChecksum], asOfDateTime)
end

#profile=(profile_hash) ⇒ Object

Raises:

  • (ArgumentError)


279
280
281
282
# File 'lib/rubydora/datastream.rb', line 279

def profile=(profile_hash)
  raise ArgumentError, "Must pass a profile, you passed #{profile_hash.class}" unless profile_hash.kind_of? Hash
  @profile = profile_hash
end

#redirect?boolean

Returns is this a redirect datastream?.

Returns:

  • (boolean)

    is this a redirect datastream?



348
349
350
# File 'lib/rubydora/datastream.rb', line 348

def redirect?
  controlGroup == 'R'
end

#saveRubydora::Datastream

Modify or save the datastream



311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/rubydora/datastream.rb', line 311

def save
  check_if_read_only
  run_callbacks :save do
    raise RubydoraError.new("Unable to save #{self.inspect} without content") unless has_content?
    if new?
      create
    else
      p = repository.modify_datastream(to_api_params.merge({ :pid => pid, :dsid => dsid })) || {}
      reset_profile_attributes
      self.profile= p unless p.empty?
      self.class.new(digital_object, dsid, @options)
    end
  end
end

#stream(from = 0, length = nil) ⇒ Object

Returns a streaming response of the datastream. This is ideal for large datasteams because it doesn’t require holding the entire content in memory. If you specify the from and length parameters it simulates a range request. Unfortunatly Fedora 3 doesn’t have range requests, so this method needs to download the whole thing and just seek to the part you care about.

Parameters:

  • from (Integer) (defaults to: 0)

    (bytes) the starting point you want to return.



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/rubydora/datastream.rb', line 234

def stream (from = 0, length = nil)
  counter = 0
  Enumerator.new do |blk|
    repository.datastream_dissemination(:pid => pid, :dsid => dsid) do |response|
      unless length
        size = external? ? entity_size(response) : dsSize
        raise "Can't determine content length" unless size
        length = size - from
      end
      response.read_body do |chunk|
        last_counter = counter
        counter += chunk.size
        if (counter > from) # greater than the range minimum
          if counter > from + length
            # At the end of what we need. Write the beginning of what was read.
            offset = (length + from) - counter - 1
            blk << chunk[0..offset]
          elsif from >= last_counter
            # At the end of what we beginning of what we need. Write the end of what was read.
            offset = from - last_counter
            blk << chunk[offset..-1]
          else
            # In the middle. We need all of this
            blk << chunk
          end
        end
      end
    end
  end
end

#urlString

Get the URL for the datastream content

Returns:

  • (String)


166
167
168
169
170
# File 'lib/rubydora/datastream.rb', line 166

def url
  options = { }
  options[:asOfDateTime] = asOfDateTime if asOfDateTime
  repository.datastream_url(pid, dsid, options) + "/content"
end

#versionsObject



284
285
286
287
288
289
# File 'lib/rubydora/datastream.rb', line 284

def versions
  versions = repository.versions_for_datastream(pid, dsid)
  versions.map do |asOfDateTime, profile|
    self.class.new(@digital_object, dsid, asOfDateTime: asOfDateTime, profile: profile)
  end
end