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)



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

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



81
82
83
# File 'lib/rubydora/datastream.rb', line 81

def self.default_attributes
  DS_DEFAULT_ATTRIBUTES
end

Instance Method Details

#asOfDateTime(asOfDateTime = nil) ⇒ Object



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

def asOfDateTime asOfDateTime = nil
  if asOfDateTime == nil
    return @asOfDateTime
  end

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

#changed?Boolean

Returns:

  • (Boolean)


199
200
201
# File 'lib/rubydora/datastream.rb', line 199

def changed?
  super || content_changed?
end

#contentObject Also known as: read

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



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

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)


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

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

#content_changed?Boolean

Returns:

  • (Boolean)


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

def content_changed?
  return false if ['E','R'].include? controlGroup
  return true if new? and !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

#createRubydora::Datastream

Add datastream to Fedora



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

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)


282
283
284
285
286
# File 'lib/rubydora/datastream.rb', line 282

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

#datastream_contentObject



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

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



326
327
328
# File 'lib/rubydora/datastream.rb', line 326

def datastream_will_change!
  attribute_will_change! :profile
end

#default_attributesObject



85
86
87
# File 'lib/rubydora/datastream.rb', line 85

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

#default_attributes=(attributes) ⇒ Object



89
90
91
# File 'lib/rubydora/datastream.rb', line 89

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

#deleteRubydora::Datastream

Purge the datastream from Fedora

Returns:



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

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)


217
218
219
# File 'lib/rubydora/datastream.rb', line 217

def empty?
  !has_content?
end

#external?boolean

Returns is this an external datastream?.

Returns:

  • (boolean)

    is this an external datastream?



331
332
333
# File 'lib/rubydora/datastream.rb', line 331

def external?
  controlGroup == 'E'
end

#has_content?Boolean

Returns:

  • (Boolean)


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

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' and 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?



346
347
348
# File 'lib/rubydora/datastream.rb', line 346

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)


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

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?



341
342
343
# File 'lib/rubydora/datastream.rb', line 341

def managed?
  controlGroup == 'M'
end

#new?Boolean

Does this datastream already exist?

Returns:

  • (Boolean)


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

def new?
  digital_object.nil? || digital_object.new_record? || profile.empty?
end

#pidObject

Helper method to get digital object pid



115
116
117
# File 'lib/rubydora/datastream.rb', line 115

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



259
260
261
262
263
264
265
266
267
268
# File 'lib/rubydora/datastream.rb', line 259

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)


270
271
272
273
# File 'lib/rubydora/datastream.rb', line 270

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?



336
337
338
# File 'lib/rubydora/datastream.rb', line 336

def redirect?
  controlGroup == 'R'
end

#saveRubydora::Datastream

Modify or save the datastream



302
303
304
305
306
307
308
309
310
311
312
# File 'lib/rubydora/datastream.rb', line 302

def save
  check_if_read_only
  run_callbacks :save do
    raise RubydoraError.new("Unable to save #{self.inspect} without content") unless has_content?
    return create if new?
    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

#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.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/rubydora/datastream.rb', line 228

def stream (from = 0, length = nil)
  raise "Can't determine dsSize" unless dsSize
  length = dsSize - from unless length
  counter = 0
  Enumerator.new do |blk|
    repository.datastream_dissemination(:pid => pid, :dsid => dsid) do |response|
      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)


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

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

#versionsObject



275
276
277
278
279
280
# File 'lib/rubydora/datastream.rb', line 275

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