Module: Excavate::Filesystem
- Defined in:
- lib/excavate/filesystem.rb
Overview
Filesystem helpers that absorb Windows file-locking quirks.
On Windows, files and directories may be transiently locked by
antivirus, indexing, or other processes immediately after they are
written. Naive FileUtils.rm / rm_rf can raise Errno::EACCES
or Errno::ENOTEMPTY. These helpers retry a small number of times
before giving up, which is enough to ride out the brief lock
windows seen in CI.
Constant Summary collapse
- RETRYABLE_ERRORS =
[Errno::EACCES, Errno::ENOTEMPTY].freeze
- DEFAULT_MAX_RETRIES =
5- DEFAULT_RETRY_DELAY =
0.2
Class Method Summary collapse
- .remove(path, max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) ⇒ Object
- .remove_recursive(path, max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) ⇒ Object
-
.replace_with_contents(archive_path, contents_dir) ⇒ Object
Replace
archive_pathwith the contents ofcontents_dir, absorbing Windows file-locking quirks. -
.scatter_contents(source_dir, destination_dir) ⇒ Object
Copy each file from
source_dirintodestination_dir, skipping any name that already exists. -
.with_retry(max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) ⇒ Object
Run
block, retrying the documented retryable errno errors up tomax_retriestimes withdelayseconds between attempts.
Class Method Details
.remove(path, max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) ⇒ Object
19 20 21 22 |
# File 'lib/excavate/filesystem.rb', line 19 def remove(path, max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) with_retry(max_retries: max_retries, delay: delay) { FileUtils.rm(path) } end |
.remove_recursive(path, max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) ⇒ Object
24 25 26 27 28 29 |
# File 'lib/excavate/filesystem.rb', line 24 def remove_recursive(path, max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) with_retry(max_retries: max_retries, delay: delay) do FileUtils.rm_rf(path) end end |
.replace_with_contents(archive_path, contents_dir) ⇒ Object
Replace archive_path with the contents of contents_dir,
absorbing Windows file-locking quirks. The optimistic path is
delete-then-rename; if the target file is locked we degrade to
copying each extracted file next to it so the user still has
access to the data.
36 37 38 39 40 41 |
# File 'lib/excavate/filesystem.rb', line 36 def replace_with_contents(archive_path, contents_dir) remove(archive_path) FileUtils.mv(contents_dir, archive_path) rescue Errno::EACCES scatter_contents(contents_dir, File.dirname(archive_path)) end |
.scatter_contents(source_dir, destination_dir) ⇒ Object
Copy each file from source_dir into destination_dir, skipping
any name that already exists. The degraded fallback used when an
archive cannot be moved into place because it is locked.
61 62 63 64 65 66 67 68 |
# File 'lib/excavate/filesystem.rb', line 61 def scatter_contents(source_dir, destination_dir) Dir.glob(File.join(source_dir, "**", "*")).each do |src| next unless File.file?(src) dest = File.join(destination_dir, File.basename(src)) FileUtils.cp(src, dest) unless File.exist?(dest) end end |
.with_retry(max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) ⇒ Object
Run block, retrying the documented retryable errno errors up
to max_retries times with delay seconds between attempts.
45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/excavate/filesystem.rb', line 45 def with_retry(max_retries: DEFAULT_MAX_RETRIES, delay: DEFAULT_RETRY_DELAY) attempts = 0 begin yield rescue *RETRYABLE_ERRORS => e attempts += 1 raise e if attempts >= max_retries sleep(delay) retry end end |