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.



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

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.



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

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



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

def history
  @history
end

#originalObject (readonly)

The path to the original file of self.



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

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.



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

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 raise MisplacedVersionFileError if it is in another directory.



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

def <<(version_info)
  version, info = Versions::Validator[version_info, self]
  version ||= @current
  @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)


99
100
101
# File 'lib/file_pipeline/versioned_file.rb', line 99

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.



105
106
107
108
109
# File 'lib/file_pipeline/versioned_file.rb', line 105

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.



113
114
115
# File 'lib/file_pipeline/versioned_file.rb', line 113

def current
  versions.last || original
end

#current_extensionObject

Returns the file extension for the #current file.



118
119
120
# File 'lib/file_pipeline/versioned_file.rb', line 118

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.



124
125
126
# File 'lib/file_pipeline/versioned_file.rb', line 124

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.



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

def finalize(overwrite: false)
  yield(self) if block_given?
  return original unless changed?

  filename = overwrite ? replacing_target : preserving_target
  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. ++



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

def (for_version: :current)
  file = case for_version
         when :current, :original
           public_send for_version
         else
           for_version
         end
  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).



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

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

#original_dirObject

Returns the directory where #original is stored.



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

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.



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

def 
  return unless changed?

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