Class: Metaclean::Committer
- Inherits:
-
Object
- Object
- Metaclean::Committer
- Defined in:
- lib/metaclean/committer.rb
Instance Method Summary collapse
- #backup_original(final_path, source_stat) ⇒ Object
- #build_clean_path(file) ⇒ Object
- #cleanup_staging(staging) ⇒ Object
- #collision_safe(path) ⇒ Object
- #commit!(staging, final_path, source_stat:) ⇒ Object
- #copy_file_exclusive(source, destination, preserve: false, expected: nil) ⇒ Object
- #copy_with_collision_safe_name(source, preferred, expected: nil) ⇒ Object
- #hardlink_with_collision_safe_name(source, preferred) ⇒ Object
-
#initialize(in_place: false, dry_run: false) ⇒ Committer
constructor
A new instance of Committer.
- #link_with_collision_safe_name(staging, preferred) ⇒ Object
- #resolve_final_path(file) ⇒ Object
- #staging_path_for(final_path) ⇒ Object
Constructor Details
#initialize(in_place: false, dry_run: false) ⇒ Committer
Returns a new instance of Committer.
8 9 10 11 |
# File 'lib/metaclean/committer.rb', line 8 def initialize(in_place: false, dry_run: false) @in_place = in_place @dry_run = dry_run end |
Instance Method Details
#backup_original(final_path, source_stat) ⇒ Object
59 60 61 62 63 64 65 66 67 68 |
# File 'lib/metaclean/committer.rb', line 59 def backup_original(final_path, source_stat) backup = hardlink_with_collision_safe_name(final_path, "#{final_path}.bak") unless FileOps.same_identity?(File.lstat(backup), source_stat) File.delete(backup) raise Error, "#{final_path} changed during cleaning — refusing to back it up" end backup rescue Errno::EACCES, Errno::EPERM, Errno::ENOTSUP, Errno::EMLINK, NotImplementedError => e raise Error, "Cannot create a metadata-preserving hard-link backup: #{e.}; use default copy mode" end |
#build_clean_path(file) ⇒ Object
109 110 111 112 113 |
# File 'lib/metaclean/committer.rb', line 109 def build_clean_path(file) extension = File.extname(file) base = File.basename(file, extension) File.join(File.dirname(file), "#{base}#{Metaclean::CLEAN_SUFFIX}#{extension}") end |
#cleanup_staging(staging) ⇒ Object
33 34 35 36 37 38 |
# File 'lib/metaclean/committer.rb', line 33 def cleanup_staging(staging) return unless staging workspace = File.dirname(staging) FileUtils.remove_entry(workspace, true) if File.exist?(workspace) end |
#collision_safe(path) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/metaclean/committer.rb', line 115 def collision_safe(path) return path unless FileOps.lexist?(path) extension = File.extname(path) base = File.basename(path, extension) directory = File.dirname(path) index = 1 loop do candidate = File.join(directory, "#{base}_#{index}#{extension}") return candidate unless FileOps.lexist?(candidate) index += 1 end end |
#commit!(staging, final_path, source_stat:) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/metaclean/committer.rb', line 40 def commit!(staging, final_path, source_stat:) backup = nil committed = false if @in_place FileOps.ensure_same_file!(final_path, source_stat) backup = backup_original(final_path, source_stat) File.rename(staging, final_path) committed = true return { path: final_path, backup: backup } end { path: link_with_collision_safe_name(staging, final_path), backup: nil } rescue SystemCallError, Interrupt if backup && !committed && File.exist?(staging) && FileOps.lexist?(backup) File.delete(backup) end raise end |
#copy_file_exclusive(source, destination, preserve: false, expected: nil) ⇒ Object
105 106 107 |
# File 'lib/metaclean/committer.rb', line 105 def copy_file_exclusive(source, destination, preserve: false, expected: nil) FileOps.copy_exclusive(source, destination, preserve: preserve, expected: expected) end |
#copy_with_collision_safe_name(source, preferred, expected: nil) ⇒ Object
95 96 97 98 99 100 101 102 103 |
# File 'lib/metaclean/committer.rb', line 95 def copy_with_collision_safe_name(source, preferred, expected: nil) target = preferred loop do copy_file_exclusive(source, target, preserve: true, expected: expected) return target rescue Errno::EEXIST target = collision_safe(preferred) end end |
#hardlink_with_collision_safe_name(source, preferred) ⇒ Object
70 71 72 73 74 75 76 77 78 |
# File 'lib/metaclean/committer.rb', line 70 def hardlink_with_collision_safe_name(source, preferred) target = preferred loop do File.link(source, target) return target rescue Errno::EEXIST target = collision_safe(preferred) end end |
#link_with_collision_safe_name(staging, preferred) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/metaclean/committer.rb', line 80 def link_with_collision_safe_name(staging, preferred) target = preferred loop do File.link(staging, target) File.delete(staging) return target rescue Errno::EEXIST target = collision_safe(preferred) rescue Errno::EACCES, Errno::EPERM, Errno::ENOTSUP, Errno::EMLINK, NotImplementedError target = copy_with_collision_safe_name(staging, target) File.delete(staging) return target end end |
#resolve_final_path(file) ⇒ Object
13 14 15 16 17 |
# File 'lib/metaclean/committer.rb', line 13 def resolve_final_path(file) return file if @in_place collision_safe(build_clean_path(file)) end |
#staging_path_for(final_path) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/metaclean/committer.rb', line 19 def staging_path_for(final_path) prefix = "#{Metaclean::TMP_MARKER}runner.#{Process.pid}." workspace = if @dry_run Dir.mktmpdir(prefix) else Dir.mktmpdir(prefix, File.dirname(final_path)) end FileOps.secure_workspace!(workspace) File.join(workspace, "staging#{File.extname(final_path)}") rescue StandardError, Interrupt FileUtils.remove_entry(workspace, true) if workspace && File.exist?(workspace) raise end |