class FileDiscard::Discarder

The core logic for moving files to an appropriate trash directory.

Public Class Methods

new(home, home_trash, mountpoint_trash_fmt) click to toggle source
# File lib/file_discard.rb, line 96
def initialize(home, home_trash, mountpoint_trash_fmt)
  home = pathname_for(home).expand_path
  @home_trash = home.join(home_trash)
  @home_mountpoint = mountpoint_of home
  @mountpoint_trash_fmt = mountpoint_trash_fmt
end

Public Instance Methods

discard(obj, options = {}) click to toggle source

Request that obj be moved to the trash.

options - a hash of any of the following:

  • :force - if greater than one, permanently remove obj

  • :directory - allow an empty directory to be discarded

  • :recursive - allow a directory to be discarded even if not empty

  • :verbose - report the move operation

May raise:

  • Errno::EINVAL - obj is “.” or “..” which are not allowed to be discarded

  • Errno::EISDIR - obj is a directory

  • Errno::ENOTEMPTY - obj is a directory with children

  • Errno::ENOENT - obj does not exist on the file system

  • TrashMissing - the trash directory for the mountpoint associated with obj did not exist

# File lib/file_discard.rb, line 118
def discard(obj, options = {})
  pn = pathname_for obj
  if pn.directory?
    SPECIAL_DIRS.include?(pn.basename.to_s) and
      raise Errno::EINVAL.new(SPECIAL_DIRS.join(' and ') << ' may not be removed')
    unless options[:recursive]
      options[:directory] or raise Errno::EISDIR.new(pn.to_s)
      pn.children.any? and raise Errno::ENOTEMPTY.new(pn.to_s)
    end
  end

  if options.key?(:force) && options[:force] > 1
    FileUtils.rm_rf(pn, {verbose: options[:verbose] || false})
    return
  end

  trash = find_trash_for pn
  unless trash.exist?
    FileDiscard.create_trash_when_missing or raise TrashMissing.new(trash.to_s)
    begin
      trash.mkpath
    rescue Errno::EACCES
      raise TrashNotPermitted.new("Unable to create #{trash.to_s}")
    end
  end

  move_options = options.has_key?(:verbose) ? {verbose: options[:verbose]} : {}
  move(pn, trash, move_options)
end

Private Instance Methods

find_trash_for(pn) click to toggle source
# File lib/file_discard.rb, line 154
def find_trash_for(pn)
  pn_path = pn.expand_path
  if pn_path.symlink?
    # Use the containing directory's real path for symbolic links, not the target of the link
    pd = pn_path.dirname.realpath
  else
    pd = pn_path.realpath.dirname
  end
  mp = mountpoint_of(pd)
  return @home_trash if mp == @home_mountpoint
  mp.join(@mountpoint_trash_fmt % Process.uid)
end
mountpoint_of(pn) click to toggle source
# File lib/file_discard.rb, line 149
def mountpoint_of(pn)
  pn = pn.parent until pn.mountpoint?
  pn
end
move(src, dst, options) { |src, dst| ... } click to toggle source
# File lib/file_discard.rb, line 177
def move(src, dst, options)
  src = src.expand_path
  dst = uniquify(dst.join(src.basename))
  options[:verbose] and puts "#{src} => #{dst}"
  File.rename(src, dst)
  block_given? and yield(src, dst)
end
pathname_for(obj) click to toggle source
# File lib/file_discard.rb, line 167
def pathname_for(obj)
  if obj.is_a? Pathname
    obj
  elsif obj.respond_to? :to_path
    Pathname.new(obj.to_path)
  else
    Pathname.new(obj)
  end
end
uniquify(pn) click to toggle source
# File lib/file_discard.rb, line 185
def uniquify(pn)
  pn.exist? or return pn

  dn   = pn.dirname
  ext  = pn.extname
  base = pn.basename(ext).to_s

  fmt = bfmt = '%H.%M.%S'

  10.times do |i|
    ts = Time.now.strftime(fmt)
    pn = dn.join("#{base} #{ts}#{ext}")
    pn.exist? or return pn
    fmt = bfmt + ".%#{i}N" # use fractional seconds, with increasing precision
  end

  raise RuntimeError.new(%{Unable to uniquify "#{base}" (last attempt: #{pn})})
end