Class: Textus::Store::Mover

Inherits:
Object
  • Object
show all
Defined in:
lib/textus/store/mover.rb

Overview

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

Instance Method Summary collapse

Constructor Details

#initialize(store) ⇒ Mover

Returns a new instance of Mover.



7
8
9
# File 'lib/textus/store/mover.rb', line 7

def initialize(store)
  @store = store
end

Instance Method Details

#call(old_key, new_key, as: Role::DEFAULT, dry_run: false) ⇒ Object

Raises:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/textus/store/mover.rb', line 11

def call(old_key, new_key, as: Role::DEFAULT, dry_run: false)
  @store.manifest.validate_key!(old_key)
  @store.manifest.validate_key!(new_key)
  raise UsageError.new("mv: old and new keys are identical") if old_key == new_key

  old_mentry, old_path, = @store.manifest.resolve(old_key)
  raise UnknownKey.new(old_key) unless File.exist?(old_path)

  new_mentry, new_path, = @store.manifest.resolve(new_key)

  if old_mentry.zone != new_mentry.zone
    raise UsageError.new(
      "mv: cross-zone move refused (#{old_mentry.zone}#{new_mentry.zone}). " \
      "Use put+delete for cross-zone moves.",
    )
  end
  if old_mentry.format != new_mentry.format
    raise UsageError.new(
      "mv: format mismatch (#{old_mentry.format}#{new_mentry.format}); refusing.",
    )
  end

  writers = @store.manifest.zone_writers(old_mentry.zone)
  raise WriteForbidden.new(old_key, old_mentry.zone, writers: writers) unless writers.include?(as)

  raise UsageError.new("mv: target '#{new_key}' already exists at #{new_path}") if File.exist?(new_path)

  # Mint uid before the move so the audit row carries it.
  pre_env = @store.get(old_key)
  current_uid = pre_env["uid"]
  etag_before = pre_env["etag"]

  if dry_run
    return {
      "protocol" => PROTOCOL, "ok" => true, "dry_run" => true,
      "from_key" => old_key, "to_key" => new_key,
      "from_path" => old_path, "to_path" => new_path,
      "uid" => current_uid
    }
  end

  if current_uid.nil?
    # Write the uid in place first so the source file carries it before mv.
    pre_env = @store.put(old_key,
                         meta: pre_env["_meta"],
                         body: pre_env["body"],
                         content: pre_env["content"],
                         as: as,
                         suppress_events: true)
    current_uid = pre_env["uid"]
    etag_before = pre_env["etag"]
  end

  FileUtils.mkdir_p(File.dirname(new_path))
  FileUtils.mv(old_path, new_path)
  rewrite_name_for_mv!(new_mentry, new_path, new_key)
  etag_after = Etag.for_file(new_path)

  @store.audit_log.append(
    role: as, verb: "mv", key: new_key,
    etag_before: etag_before, etag_after: etag_after,
    extras: {
      "from_key" => old_key, "to_key" => new_key,
      "from_path" => old_path, "to_path" => new_path,
      "uid" => current_uid
    }
  )

  env = @store.get(new_key)
  {
    "protocol" => PROTOCOL, "ok" => true,
    "from_key" => old_key, "to_key" => new_key,
    "from_path" => old_path, "to_path" => new_path,
    "uid" => current_uid,
    "envelope" => env
  }
end