Class: Gemvault::Vault

Inherits:
Object
  • Object
show all
Extended by:
Forwardable, VaultSession
Defined in:
lib/gemvault/vault.rb

Overview

The public vault interface. Delegates storage to a backend chosen by file format: a Dbvault (SQLite) for existing SQLite files, a read-only LegacyTarvault for a tarball still indexed by manifest.json, and a Tarvault for every other tarball. New vaults are Tarvaults. Only the selected backend is loaded, so the tar path never requires sqlite3.

Defined Under Namespace

Classes: DuplicateGemError, Error, InvalidGemError, NotFoundError, ReadOnlyError, UnsupportedVersionError

Constant Summary collapse

SQLITE_MAGIC =
"SQLite format 3#{0.chr}".freeze
TAR_MAGIC =
"ustar".freeze
TAR_MAGIC_OFFSET =
257
CURRENT_FORMAT =
3
MIN_READABLE_FORMAT =
1

Class Method Summary collapse

Instance Method Summary collapse

Methods included from VaultSession

open

Constructor Details

#initialize(path, create: false) ⇒ Vault

Returns a new instance of Vault.



116
117
118
119
# File 'lib/gemvault/vault.rb', line 116

def initialize(path, create: false)
  absolute_path = Pathname(path).expand_path
  @backend = self.class.backend_for(absolute_path, create:)
end

Class Method Details

.assert_readable!(version:, path:) ⇒ Object



39
40
41
42
43
44
# File 'lib/gemvault/vault.rb', line 39

def self.assert_readable!(version:, path:)
  return if version.between?(MIN_READABLE_FORMAT, CURRENT_FORMAT)

  raise UnsupportedVersionError,
        "Vault #{path} is format #{version}; this gemvault reads up to #{CURRENT_FORMAT}. Upgrade gemvault."
end

.backend_for(path, create:) ⇒ Object

Raises:



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/gemvault/vault.rb', line 46

def self.backend_for(path, create:)
  return build_tarvault(path, create: true) if create
  raise NotFoundError, "Vault not found: #{path}" unless path.exist?

  case container_kind(path)
  when :sqlite then build_dbvault(path)
  when :tar then build_tarvault(path, create: false)
  else
    raise Error, "Unrecognized vault format: #{path} (not a Dbvault or Tarvault; it may require a newer gemvault)"
  end
end

.build_dbvault(path) ⇒ Object



76
77
78
79
80
81
82
83
84
85
# File 'lib/gemvault/vault.rb', line 76

def self.build_dbvault(path)
  begin
    require_relative "dbvault"
    Dbvault.new(path)
  rescue LoadError => e
    raise Error,
          "#{path} is a legacy SQLite vault; it needs the sqlite3 gem (#{e.message}). " \
          "Install sqlite3, or upgrade the vault with a gemvault that includes it."
  end
end

.build_tarvault(path, create:) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/gemvault/vault.rb', line 87

def self.build_tarvault(path, create:)
  require_relative "tarvault"
  return Tarvault.new(path, create:) if create || !legacy_tarvault?(path)

  require_relative "legacy_tarvault"
  LegacyTarvault.new(path)
end

.container_kind(path) ⇒ Object



58
59
60
61
62
63
# File 'lib/gemvault/vault.rb', line 58

def self.container_kind(path)
  return :sqlite if sqlite?(path)
  return :tar if tar?(path)

  :unknown
end

.create(path) ⇒ Object

Creates an empty vault file at path, leaving it closed.



35
36
37
# File 'lib/gemvault/vault.rb', line 35

def self.create(path)
  new(path, create: true).close
end

.legacy_tarvault?(path) ⇒ Boolean

A tarball indexed the way vaults were through format 2. Asked by name rather than by version, because the version is recorded in the very index this gemvault no longer reads.

An archive too damaged to enumerate answers no rather than raising here. This runs ahead of every backend, so a wreck raised from it would escape the rescue Tarvault opens with and reach the user as a tar library's backtrace; declining instead leaves that path to report it as the Vault::Error it has always been.

Returns:

  • (Boolean)


104
105
106
107
108
109
110
111
112
113
114
# File 'lib/gemvault/vault.rb', line 104

def self.legacy_tarvault?(path)
  require_relative "manifest_text"
  require_relative "tarball"

  begin
    names = Tarball.new(path).names
    !names.include?(ManifestText::FILENAME) && names.include?(ManifestText::LEGACY_FILENAME)
  rescue Gem::Package::Error, ArgumentError, Errno::EINVAL
    false
  end
end

.sqlite?(path) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/gemvault/vault.rb', line 65

def self.sqlite?(path)
  path.exist? && path.binread(SQLITE_MAGIC.bytesize) == SQLITE_MAGIC
end

.tar?(path) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
72
73
74
# File 'lib/gemvault/vault.rb', line 69

def self.tar?(path)
  return false unless path.exist?

  header = path.binread(TAR_MAGIC_OFFSET + TAR_MAGIC.bytesize)
  header.to_s[TAR_MAGIC_OFFSET, TAR_MAGIC.bytesize] == TAR_MAGIC
end