Class: FilePipeline::VersionedFile

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
FileOperations::ExifManipulable
Defined in:
lib/file_pipeline/versioned_file.rb

Overview

VersionedFile creates a directory where it stores any versions of file.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from FileOperations::ExifManipulable

#delete_tags, file_tags, #missing_exif_fields, #parse_exif_errors, parse_tag_error, #read_exif, strip_path, #write_exif

Constructor Details

#initialize(file, target_suffix: SecureRandom.hex(4)) ⇒ VersionedFile

Returns a new instance with file as the #original.

Arguments
  • file - Path to the file the instance will be based on. That file should not be touched unless #finalize is called with the :overwrite option set to true.

Caveat it can not be ruled out that buggy or malignant file operations modify the original.

Options

<tt>target_suffix</ttm> is a string to be appended to the file that will be written by #finalize (the last version) if #finalize is to preserve the original. It is recommended to use a randomized string (default) to avoid clashes with other files in the directory.



68
69
70
71
72
73
74
75
76
77
# File 'lib/file_pipeline/versioned_file.rb', line 68

def initialize(file, target_suffix: SecureRandom.hex(4))
  raise Errors::MissingVersionFileError, file: file unless File.exist? file

  @original = file
  @basename = File.basename(file, '.*')
  @history = Versions::History.new
  @directory = nil
  @target_suffix = target_suffix
  history[original] = nil
end

Instance Attribute Details

#basenameObject (readonly)

The basename of the versioned file.



9
10
11
# File 'lib/file_pipeline/versioned_file.rb', line 9

def basename
  @basename
end

#historyObject (readonly)

A hash with file paths as keys, information on the modifications applied to create the version as values (instances of FileOperations::Results).



13
14
15
# File 'lib/file_pipeline/versioned_file.rb', line 13

def history
  @history
end

#originalObject (readonly)

The path to the original file of self.



16
17
18
# File 'lib/file_pipeline/versioned_file.rb', line 16

def original
  @original
end

#target_suffixObject (readonly)

A String that is appended to the file basename when the file written by #finalize is not replacing the original.



20
21
22
# File 'lib/file_pipeline/versioned_file.rb', line 20

def target_suffix
  @target_suffix
end

Instance Method Details

#<<(version_info) ⇒ Object

Adds a new version to #history and returns self.

version_info must be a path to an existing file or an array with the path and optionally a FileOperations::Results instance: ['path/to/file', results_object]. Will move the file to #directory if it is in another directory.



85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/file_pipeline/versioned_file.rb', line 85

def <<(version_info)
  file, info = version_info
  if info&.failure
    raise Errors::FailedModificationError, info: info, file: original
  end

  version = validate(file)
  @history[version] = info
  self
rescue StandardError => e
  reset
  raise e
end

#changed?Boolean

Returns true if there are #versions (file has been modified).

Warning: It will also return true if the file has been cloned.

Returns:

  • (Boolean)


102
103
104
# File 'lib/file_pipeline/versioned_file.rb', line 102

def changed?
  current != original
end

#cloneObject Also known as: touch

Creates a new identical version of #current. Will only add the path of the file to history, but no FileOperations::Results.



108
109
110
111
112
# File 'lib/file_pipeline/versioned_file.rb', line 108

def clone
  filename = FilePipeline.new_basename + current_extension
  clone_file = Versions.copy(current, directory, filename)
  self << clone_file
end

#currentObject

Returns the path to the current file or the #original if no versions have been created.



116
117
118
# File 'lib/file_pipeline/versioned_file.rb', line 116

def current
  versions.last || original
end

#current_extensionObject

Returns the file extension for the #current file.



121
122
123
# File 'lib/file_pipeline/versioned_file.rb', line 121

def current_extension
  File.extname current
end

#directoryObject

Returns the path to the directory where the versioned of self are stored. Creates the directory if it does not exist.



127
128
129
# File 'lib/file_pipeline/versioned_file.rb', line 127

def directory
  @directory ||= workdir
end

#finalize(overwrite: false) ⇒ Object

Writes the #current version to #basename, optionally the #target_suffix, and the #current_extension in #original_dir. Deletes all versions and resets the #history to an empty Hash. Returns the path to the written file.

If the optional block is passed, it will be evaluated before the file is finalized.

Options
  • overwrite - true or false

    • false (default) - The #target_suffix will be appended to the #basename and the #original will be preserved.

    • true - The finalized version will replace the #original.



145
146
147
148
149
150
151
152
# File 'lib/file_pipeline/versioned_file.rb', line 145

def finalize(overwrite: false)
  yield(self) if block_given?
  filename = overwrite ? replacing_trarget : preserving_taget
  FileUtils.rm original if overwrite
  @original = Versions.copy(current, original_dir, filename)
ensure
  reset
end

#metadata(for_version: :current) ⇒ Object

Returns the Exif metadata

Options
  • :for_version - current or original

    • current (default) - Metadata for the #current file will be returned.

    • original - Metadata for the #original file will be returned.

– TODO: when file is not an image file, this should return other metadata than exif. ++



167
168
169
170
171
172
173
# File 'lib/file_pipeline/versioned_file.rb', line 167

def (for_version: :current)
  if %i[current original].include? for_version
    file = public_send(for_version)
  end
  file ||= for_version
  read_exif(file).first
end

#modifyObject

Creates a new version. Requires a block that must return a path to an existing file or an array with the path and optionally a FileOperations::Results instance: ['path/to/file', results_object].

The actual file modification logic will be in the block.

The block must take three arguments: for the #current file (from which the modified version will be created), the work #directory (to where the modified file will be written), and the #original file (which will only be used in modifications that need the original file for reference, such as modifications that restore file metadata that was lost in other modifications).



188
189
190
# File 'lib/file_pipeline/versioned_file.rb', line 188

def modify
  self << yield(current, directory, original)
end

#original_dirObject

Returns the directory where #original is stored.



193
194
195
# File 'lib/file_pipeline/versioned_file.rb', line 193

def original_dir
  File.dirname original
end

#recovered_metadataObject

Returns a hash into which all captured data from file operations with the FileOperations::CapturedDataTags::DROPPED_EXIF_DATA has been merged.



199
200
201
202
203
204
# File 'lib/file_pipeline/versioned_file.rb', line 199

def 
  return unless changed?

  captured_data_with(FileOperations::CapturedDataTags::DROPPED_EXIF_DATA)
    &.reduce({}) { |recovered, data| recovered.merge data }
end