lib/image_optim/path.rb
# frozen_string_literal: true
require 'fspath'
require 'image_optim/image_meta'
class ImageOptim
# FSPath with additional helpful methods
class Path < FSPath
NULL = if defined?(IO::NULL)
IO::NULL
else
%w[/dev/null NUL: NUL nul NIL: NL:].find{ |dev| File.exist?(dev) }
end
# Get temp path for this file with same extension
def temp_path(*args, &block)
ext = extname
self.class.temp_file_path([basename(ext).to_s, ext], *args, &block)
end
# Copy file to dst, optionally preserving attributes
#
# See FileUtils.copy_file
def copy(dst, preserve = false)
FileUtils.copy_file(self, dst, preserve)
end
# Move file to dst: rename on same device, copy and unlink original
# otherwise
#
# See FileUtils.mv
def move(dst)
FileUtils.move(self, dst)
end
# Copy metadata: uid, gid, mode, optionally atime and mtime
#
# Adapted from FileUtils::Entry_#copy_metadata by Minero Aoki
def copy_metadata(dst, time = false)
stat = lstat
dst.utime(stat.atime, stat.mtime) if time
begin
dst.chown(stat.uid, stat.gid)
rescue Errno::EPERM, Errno::EACCES
dst.chmod(stat.mode & 0o1777)
else
dst.chmod(stat.mode)
end
end
# Atomic replace dst with self
def replace(dst)
dst = self.class.convert(dst)
if same_dev?(dst.dirname)
dst.copy_metadata(self)
begin
rename(dst.to_s)
rescue Errno::EXDEV
replace_using_tmp_file(dst)
end
else
replace_using_tmp_file(dst)
end
end
# Get format using ImageSize
def image_format
ImageMeta.format_for_path(self)
end
# Returns path if it is already an instance of this class otherwise new
# instance
def self.convert(path)
path.is_a?(self) ? path : new(path)
end
protected
def same_dev?(other)
stat.dev == other.stat.dev
end
def replace_using_tmp_file(dst)
dst.temp_path_with_tmp_ext(dst.dirname) do |temp|
move(temp)
dst.copy_metadata(temp)
temp.rename(dst.to_s)
end
end
def temp_path_with_tmp_ext(*args, &block)
self.class.temp_file_path([basename.to_s, '.tmp'], *args, &block)
end
end
end