Class: Valkyrie::Storage::VersionedDisk

Inherits:
Object
  • Object
show all
Defined in:
lib/valkyrie/storage/versioned_disk.rb

Overview

The VersionedDisk adapter implements versioned storage on disk by storing the timestamp of the file’s creation as part of the file name (v-timestamp-filename.jpg). If the current file is deleted it creates a DeletionMarker, which is an empty file with “deletionmarker” in the name of the file.

Defined Under Namespace

Classes: VersionId

Constant Summary collapse

PROTOCOL =
'versiondisk://'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_path:, path_generator: ::Valkyrie::Storage::Disk::BucketedStorage, file_mover: FileUtils.method(:cp)) ⇒ VersionedDisk

Returns a new instance of VersionedDisk.



12
13
14
15
16
# File 'lib/valkyrie/storage/versioned_disk.rb', line 12

def initialize(base_path:, path_generator: ::Valkyrie::Storage::Disk::BucketedStorage, file_mover: FileUtils.method(:cp))
  @base_path = Pathname.new(base_path.to_s)
  @path_generator = path_generator.new(base_path: base_path)
  @file_mover = file_mover
end

Instance Attribute Details

#base_pathObject (readonly)

Returns the value of attribute base_path.



9
10
11
# File 'lib/valkyrie/storage/versioned_disk.rb', line 9

def base_path
  @base_path
end

#file_moverObject (readonly)

Returns the value of attribute file_mover.



9
10
11
# File 'lib/valkyrie/storage/versioned_disk.rb', line 9

def file_mover
  @file_mover
end

#path_generatorObject (readonly)

Returns the value of attribute path_generator.



9
10
11
# File 'lib/valkyrie/storage/versioned_disk.rb', line 9

def path_generator
  @path_generator
end

Instance Method Details

#current_timestampObject



34
35
36
# File 'lib/valkyrie/storage/versioned_disk.rb', line 34

def current_timestamp
  Time.now.strftime("%s%L")
end

#delete(id:) ⇒ Object

Delete the file on disk associated with the given identifier.

Parameters:



90
91
92
93
94
95
96
97
98
99
# File 'lib/valkyrie/storage/versioned_disk.rb', line 90

def delete(id:)
  id = version_id(id).resolve_current
  if id.current?
    id.version_files.each do |version_id|
      FileUtils.rm_rf(version_id.file_path)
    end
  elsif File.exist?(id.file_path)
    FileUtils.rm_rf(id.file_path)
  end
end

#file_path(version_id) ⇒ Object



115
116
117
# File 'lib/valkyrie/storage/versioned_disk.rb', line 115

def file_path(version_id)
  version_id.to_s.gsub(/^#{Regexp.escape(protocol)}/, '')
end

#find_by(id:) ⇒ Valkyrie::StorageAdapter::File

Return the file associated with the given identifier

Parameters:

Returns:

Raises:

  • Valkyrie::StorageAdapter::FileNotFound if nothing is found



80
81
82
83
84
85
86
# File 'lib/valkyrie/storage/versioned_disk.rb', line 80

def find_by(id:)
  version_id = version_id(id)
  raise Valkyrie::StorageAdapter::FileNotFound if version_id.nil? || version_id&.deletion_marker?
  Valkyrie::StorageAdapter::File.new(id: version_id.current_reference_id.id, io: ::Valkyrie::Storage::Disk::LazyFile.open(version_id.file_path, 'rb'), version_id: version_id.id)
rescue Errno::ENOENT
  raise Valkyrie::StorageAdapter::FileNotFound
end

#find_versions(id:) ⇒ Array<Valkyrie::StorageAdapter::File>

Parameters:

Returns:



103
104
105
106
107
# File 'lib/valkyrie/storage/versioned_disk.rb', line 103

def find_versions(id:)
  version_files(id: id).select { |x| !x.to_s.include?("deletionmarker") }.map do |file|
    find_by(id: Valkyrie::ID.new("#{protocol}#{file}"))
  end
end

#handles?(id:) ⇒ Boolean

Returns true if this adapter can handle this type of identifer.

Parameters:

Returns:

  • (Boolean)

    true if this adapter can handle this type of identifer



60
61
62
# File 'lib/valkyrie/storage/versioned_disk.rb', line 60

def handles?(id:)
  id.to_s.start_with?("#{protocol}#{base_path}")
end

#protocolString

Returns identifier prefix.

Returns:

  • (String)

    identifier prefix



72
73
74
# File 'lib/valkyrie/storage/versioned_disk.rb', line 72

def protocol
  PROTOCOL
end

#supports?(feature) ⇒ Boolean

Returns true if the adapter supports the given feature.

Parameters:

  • feature (Symbol)

    Feature to test for.

Returns:

  • (Boolean)

    true if the adapter supports the given feature



66
67
68
69
# File 'lib/valkyrie/storage/versioned_disk.rb', line 66

def supports?(feature)
  return true if feature == :versions || feature == :version_deletion
  false
end

#upload(file:, original_filename:, resource: nil, paused: false, **extra_arguments) ⇒ Valkyrie::StorageAdapter::File

Parameters:

  • file (IO)
  • original_filename (String)
  • resource (Valkyrie::Resource) (defaults to: nil)
  • _extra_arguments (Hash)

    additional arguments which may be passed to other adapters

Returns:



23
24
25
26
27
28
29
30
31
32
# File 'lib/valkyrie/storage/versioned_disk.rb', line 23

def upload(file:, original_filename:, resource: nil, paused: false, **extra_arguments)
  version_timestamp = current_timestamp
  new_path = path_generator.generate(resource: resource, file: file, original_filename: "v-#{version_timestamp}-#{original_filename}")
  # If we've gone faster than milliseconds here, pause a millisecond and
  # re-call. Probably only an issue for test suites.
  return sleep(0.001) && upload(file: file, original_filename: original_filename, resource: resource, paused: true, **extra_arguments) if !paused && File.exist?(new_path)
  FileUtils.mkdir_p(new_path.parent)
  file_mover.call(file.try(:path) || file.try(:disk_path), new_path)
  find_by(id: Valkyrie::ID.new("#{protocol}#{new_path}"))
end

#upload_version(id:, file:, paused: false) ⇒ Object

Parameters:

  • id (Valkyrie::ID)

    ID of the Valkyrie::StorageAdapter::File to version.

  • file (IO)
  • paused (Boolean) (defaults to: false)

    set to true when upload_version had to pause for a millisecond to get a later timestamp. Internal only - do not set.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/valkyrie/storage/versioned_disk.rb', line 43

def upload_version(id:, file:, paused: false)
  version_timestamp = current_timestamp
  # Get the existing version_id so we can calculate the next path from it.
  current_version_id = version_id(id)
  current_version_id = current_version_id.version_files[1] if current_version_id.deletion_marker?
  existing_path = current_version_id.file_path
  new_path = Pathname.new(existing_path.gsub(current_version_id.version, version_timestamp.to_s))
  # If we've gone faster than milliseconds here, pause a millisecond and
  # re-call.
  return sleep(0.001) && upload_version(id: id, file: file, paused: true) if !paused && File.exist?(new_path)
  FileUtils.mkdir_p(new_path.parent)
  file_mover.call(file.try(:path) || file.try(:disk_path), new_path)
  find_by(id: Valkyrie::ID.new("#{protocol}#{new_path}"))
end

#version_files(id:) ⇒ Object



109
110
111
112
113
# File 'lib/valkyrie/storage/versioned_disk.rb', line 109

def version_files(id:)
  root = Pathname.new(file_path(id))
  id = VersionId.new(id)
  root.parent.children.select { |file| file.basename.to_s.end_with?(id.filename) }.sort.reverse
end

#version_id(id) ⇒ Object

Returns VersionId A VersionId value that’s resolved a current reference, so we can access the ‘version_id` and current reference.

Returns:

  • VersionId A VersionId value that’s resolved a current reference, so we can access the ‘version_id` and current reference.



121
122
123
124
125
# File 'lib/valkyrie/storage/versioned_disk.rb', line 121

def version_id(id)
  id = VersionId.new(id)
  return id unless id.versioned?
  id.resolve_current
end