Class: IiifPrint::Data::WorkDerivatives

Inherits:
Object
  • Object
show all
Includes:
FilesetHelper, PathHelper
Defined in:
lib/iiif_print/data/work_derivatives.rb

Overview

TODO: consider compositional refactoring (not mixins), but this

may make readability/comprehendability higher, and yield
higher applied/practical complexity.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PathHelper

#isuri?, #normalize_path, #path_to_uri, #registered_ingest_path, #validate_path

Methods included from FilesetHelper

#fileset_id, #first_fileset

Constructor Details

#initialize(work: nil, fileset: nil, parent: nil) ⇒ WorkDerivatives

Adapt work and either specific or first fileset



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/iiif_print/data/work_derivatives.rb', line 54

def initialize(work: nil, fileset: nil, parent: nil)
  # adapted context usually work, may be string id of FileSet
  @work = work
  @fileset = fileset.nil? ? first_fileset : fileset
  # computed name-to-path mapping, initially nil as sentinel for JIT load
  @paths = nil
  # assignments for attachment
  @assigned = []
  # un-assignments for deletion
  @unassigned = []
  # parent is IiifPrint::Data::WorkFile object for derivatives
  @parent = parent
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object (private)



298
299
300
301
302
303
304
305
306
# File 'lib/iiif_print/data/work_derivatives.rb', line 298

def method_missing(method, *args, &block)
  # if we proxy mapping/hash enumertion methods,
  #   make sure @paths loaded, then proxy to it.
  if respond_to_missing?(method)
    load_paths if @paths.nil?
    return @paths.send(method, *args, &block)
  end
  super
end

Class Attribute Details

.remap_namesObject

Returns the value of attribute remap_names.



37
38
39
# File 'lib/iiif_print/data/work_derivatives.rb', line 37

def remap_names
  @remap_names
end

Instance Attribute Details

#assignedArray<String>

Assigned attachment queue (of paths)

Returns:

  • (Array<String>)

    list of paths queued for attachment



26
27
28
# File 'lib/iiif_print/data/work_derivatives.rb', line 26

def assigned
  @assigned
end

#filesetFileSet

FileSet is secondary adapted context

Returns:

  • (FileSet)

    fileset for work, with regard to these derivatives



18
19
20
# File 'lib/iiif_print/data/work_derivatives.rb', line 18

def fileset
  @fileset
end

#parentIiifPrint::Data::WorkFile

Parent pointer to WorkFile object representing fileset

Returns:



22
23
24
# File 'lib/iiif_print/data/work_derivatives.rb', line 22

def parent
  @parent
end

#unassignedArray<String>

Assigned deletion queue (of destination names)

Returns:

  • (Array<String>)

    list of destination names queued for deletion



30
31
32
# File 'lib/iiif_print/data/work_derivatives.rb', line 30

def unassigned
  @unassigned
end

#workActiveFedora::Base

Work is primary adapted context

Returns:

  • (ActiveFedora::Base)

    Hyrax work-type object



14
15
16
# File 'lib/iiif_print/data/work_derivatives.rb', line 14

def work
  @work
end

Class Method Details

.data(from:, of_type:) ⇒ String

Parameters:

  • from (Object)

    the work from which we’ll extract the given type of data.

  • of_type (String)

    the type of data we want extracted from the work (e.g. “txt”, “json”)

Returns:

  • (String)


44
45
46
# File 'lib/iiif_print/data/work_derivatives.rb', line 44

def self.data(from:, of_type:)
  new(work: from).data(of_type)
end

.of(work, fileset = nil, parent = nil) ⇒ Object

alternate constructor spelling:



49
50
51
# File 'lib/iiif_print/data/work_derivatives.rb', line 49

def self.of(work, fileset = nil, parent = nil)
  new(work: work, fileset: fileset, parent: parent)
end

Instance Method Details

#assign(path) ⇒ Object

Assign a path to assigned queue for attachment

Parameters:

  • path (String)

    Path to source file



79
80
81
82
83
84
85
86
# File 'lib/iiif_print/data/work_derivatives.rb', line 79

def assign(path)
  path = normalize_path(path)
  validate_path(path)
  @assigned.push(path)
  # We are keeping assignment both in ephemeral, transient @assigned
  #   and mirroring to db to share context with other components:
  log_assignment(path, path_destination_name(path))
end

#attach(file, name) ⇒ Object

attach a single derivative file to work

Parameters:

  • file (String, IO)

    path to file or IO object

  • name (String)

    destination name, usually file extension



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/iiif_print/data/work_derivatives.rb', line 140

def attach(file, name)
  raise 'Cannot save for nil fileset' if fileset.nil?
  mkdir_pairtree
  path = path_factory.derivative_path_for_reference(fileset, name)
  # if file argument is path, copy file
  if file.is_a? String
    FileUtils.copy(file, path)
  else
    # otherwise, presume file is an IO, read, write it
    #   note: does not close input file/IO, presume that is caller's
    #   responsibility.
    orig_pos = file.tell
    file.seek(0)
    File.open(path, 'w') { |dstfile| dstfile.write(file.read) }
    file.seek(orig_pos)
  end
  # finally, reload @paths after mutation
  load_paths
end

#commit!Object

commit pending changes to work files

beginning with removals, then with new assignments


103
104
105
106
107
108
109
110
111
# File 'lib/iiif_print/data/work_derivatives.rb', line 103

def commit!
  @unassigned.each { |name| delete(name) }
  @assigned.each do |path|
    attach(path, path_destination_name(path))
  end
  # reset queues after work is complete
  @assigned = []
  @unassigned = []
end

#commit_queued!(file_set) ⇒ Object

Given a fileset meeting both of the following conditions:

1. a non-nil import_url value;
2. is attached to a work (persisted in Fedora, if not yet in Solr)...

…this method gets associated derivative paths queued and attach all.

Parameters:

  • file_set (FileSet)

    saved file set, attached to work, with identifier, and a non-nil import_url

Raises:

  • (ArgumentError)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/iiif_print/data/work_derivatives.rb', line 119

def commit_queued!(file_set)
  raise ArgumentError, 'No FileSet import_url' if file_set.import_url.nil?
  import_path = file_url_to_path(file_set.import_url)
  work = file_set.member_of.select(&:work?)[0]
  raise ArgumentError, 'Work not found for fileset' if work.nil?
  derivatives = WorkDerivatives.of(work, file_set)
  IngestFileRelation.derivatives_for_file(import_path).each do |path|
    next unless File.exist?(path)
    attachment_record = DerivativeAttachment.where(path: path).first
    derivatives.attach(path, attachment_record.destination_name)
    # update previously nil fileset id
    attachment_record.fileset_id = file_set.id
    attachment_record.save!
  end
  @fileset ||= file_set
  load_paths
end

#data(name) ⇒ String

Get raw binary or encoded text data of file as a String

Parameters:

  • name (String)

    destination name, usually file extension

Returns:

  • (String)

    Raw bytes, or if text file, a UTF-8 encoded String



226
227
228
229
230
231
232
# File 'lib/iiif_print/data/work_derivatives.rb', line 226

def data(name)
  result = ''
  with_io(name) do |io|
    result += io.read
  end
  result
end

#delete(name, force: nil) ⇒ Object

Delete a derivative file from work, by destination name

Parameters:

  • name (String)

    destination name, usually file extension



162
163
164
165
166
167
168
169
170
# File 'lib/iiif_print/data/work_derivatives.rb', line 162

def delete(name, force: nil)
  raise 'Cannot save for nil fileset' if fileset.nil?
  path = path_factory.derivative_path_for_reference(fileset, name)
  # will remove file, if it exists; won't remove pairtree, even
  #   if it becomes empty, as that is excess scope.
  FileUtils.rm(path, force: force) if File.exist?(path)
  # finally, reload @paths after mutation
  load_paths
end

#exist?(name) ⇒ TrueClass, FalseClass

Check if derivative file exists for destination name

Parameters:

  • name (String)

    optional destination name, usually file extension

Returns:

  • (TrueClass, FalseClass)

    boolean



218
219
220
221
# File 'lib/iiif_print/data/work_derivatives.rb', line 218

def exist?(name)
  # TODO: It is unclear where the #keys and and #[] methods are coming from.  There's @paths.keys referenced in this code.
  keys.include?(name) && File.exist?(self[name])
end

#load_pathsObject

Load all paths/names to @paths once, upon first access



173
174
175
176
177
178
179
180
181
182
183
# File 'lib/iiif_print/data/work_derivatives.rb', line 173

def load_paths
  fsid = fileset_id
  if fsid.nil?
    @paths = {}
    return
  end
  # list of paths
  paths = path_factory.derivatives_for_reference(fsid)
  # names from paths
  @paths = paths.map { |e| [path_destination_name(e), e] }.to_h
end

#path(name) ⇒ String, NilClass

path to existing derivative file for destination name

Parameters:

  • name (String)

    destination name, usually file extension

Returns:

  • (String, NilClass)

    path (or nil)



188
189
190
191
192
193
# File 'lib/iiif_print/data/work_derivatives.rb', line 188

def path(name)
  load_paths if @paths.nil?
  result = @paths[name]
  return if result.nil?
  File.exist?(result) ? result : nil
end

#size(name = nil) ⇒ Integer

Get number of derivatives or, if a destination name argument

is provided, the size of derivative file

Parameters:

  • name (String) (defaults to: nil)

    optional destination name, usually file extension

Returns:

  • (Integer)

    size in bytes



209
210
211
212
213
# File 'lib/iiif_print/data/work_derivatives.rb', line 209

def size(name = nil)
  load_paths if @paths.nil?
  return @paths.size if name.nil?
  File.size(@paths[name])
end

#stateString

Assignment state

Returns:

  • (String)

    A label describing the state of assignment queues



70
71
72
73
74
75
# File 'lib/iiif_print/data/work_derivatives.rb', line 70

def state
  load_paths
  return 'dirty' unless @unassigned.empty? && @assigned.empty?
  return 'empty' if @paths.keys.empty?
  'saved'
end

#unassign(name) ⇒ Object

Assign a destination name to unassigned queue for deletion – OR –

remove a path from queue of assigned items

Parameters:

  • name (String)

    Destination name (file extension), or source path



91
92
93
94
95
96
97
98
99
# File 'lib/iiif_print/data/work_derivatives.rb', line 91

def unassign(name)
  # if name is queued path, remove from @assigned queue:
  if @assigned.include?(name)
    @assigned.delete(name)
    unlog_assignment(name, path_destination_name(name))
  end
  # if name is known destination name, remove
  @unassigned.push(name) if exist?(name)
end

#with_io(name, &block) ⇒ Object

Run a block in context of the opened derivative file for reading

Parameters:

  • name (String)

    destination name, usually file extension

  • block (Proc)

    block/proc to run in context of file IO



198
199
200
201
202
203
# File 'lib/iiif_print/data/work_derivatives.rb', line 198

def with_io(name, &block)
  mode = ['xml', 'txt', 'html'].include?(name) ? 'rb:UTF-8' : 'rb'
  filepath = path(name)
  return if filepath.nil?
  File.open(filepath, mode, &block)
end