class Photein::MediaFile

Constants

DATE_FORMAT
NORMAL_EXTNAME_MAP

Attributes

path[R]

Public Class Methods

new(path) click to toggle source
# File lib/photein/media_file.rb, line 18
def initialize(path)
  @path = Pathname(path)
end

Public Instance Methods

import() click to toggle source
# File lib/photein/media_file.rb, line 22
    def import
      return if corrupted?
      return if Photein::Config.interactive && denied_by_user?
      return if Photein::Config.safe && in_use?
      return if Photein::Config.optimize_for && non_optimizable_format?

      FileUtils.mkdir_p(parent_dir, noop: Photein::Config.dry_run)

      optimize if Photein::Config.optimize_for

      Photein::Logger.info(<<~MSG.chomp)
        #{Photein::Config.keep ? 'copying' : 'moving'} #{path.basename} to #{dest_path}
      MSG

      if File.exist?(tempfile)
        FileUtils.mv(tempfile, dest_path, noop: Photein::Config.dry_run)
      else
        FileUtils.cp(path, dest_path, noop: Photein::Config.dry_run)
        FileUtils.chmod('-x', dest_path, noop: Photein::Config.dry_run)
      end

      FileUtils.rm(path, noop: Photein::Config.dry_run || Photein::Config.keep)
    end

Private Instance Methods

corrupted?(result = false) click to toggle source
# File lib/photein/media_file.rb, line 48
def corrupted?(result = false)
  return result.tap do |r|
    Photein::Logger.error("#{path.basename}: cannot import corrupted file") if r
  end
end
denied_by_user?() click to toggle source
# File lib/photein/media_file.rb, line 54
def denied_by_user?
  $stdout.printf "Import #{path}? [y/N]"
  (STDIN.getch.downcase != 'y').tap { $stdout.puts }
end
dest_extname() click to toggle source
# File lib/photein/media_file.rb, line 108
def dest_extname
  self.class::OPTIMIZATION_FORMAT_MAP
    .dig(Photein::Config.optimize_for, extname) || extname
end
dest_path() click to toggle source
# File lib/photein/media_file.rb, line 85
def dest_path
  @dest_path ||= begin
                   base_path = parent_dir.join("#{timestamp.strftime(DATE_FORMAT)}#{dest_extname}")
                   counter   = resolve_name_collision(base_path.sub_ext("*#{dest_extname}"))

                   base_path.sub_ext("#{counter}#{dest_extname}")
                 end
end
extname() click to toggle source
# File lib/photein/media_file.rb, line 113
def extname
  @extname ||= NORMAL_EXTNAME_MAP[path.extname.downcase] || path.extname.downcase
end
filename_stamp() click to toggle source
# File lib/photein/media_file.rb, line 98
def filename_stamp
  Time.parse(path.basename(path.extname).to_s)
rescue ArgumentError
  begin
    File.birthtime(path)
  rescue NotImplementedError
    File.mtime(path)
  end
end
in_use?() click to toggle source
# File lib/photein/media_file.rb, line 59
def in_use?
  out, _err, status = Open3.capture3("lsof '#{path}'")

  if status.success? # Do open files ALWAYS return exit status 0? (I think so.)
    cmd, pid = out.lines[1]&.split&.first(2)
    Photein::Logger.fatal("skipping #{path}: file in use by #{cmd} (PID #{pid})")
    return true
  else
    return false
  end
end
non_optimizable_format?() click to toggle source
# File lib/photein/media_file.rb, line 71
def non_optimizable_format? # may be overridden by subclasses
  return false
end
parent_dir() click to toggle source
# File lib/photein/media_file.rb, line 75
def parent_dir
  Pathname(Photein::Config.dest).join(timestamp.strftime('%Y'))
end
resolve_name_collision(collision_glob) click to toggle source
# File lib/photein/media_file.rb, line 117
def resolve_name_collision(collision_glob)
  case Dir[collision_glob].length
  when 0 # if no files found, no biggie
  when 1 # if one file found, WITH OR WITHOUT COUNTER, reset counter to a
    if Dir[collision_glob].first != collision_glob.sub('*', 'a') # don't try if it's already a lone, correctly-countered file
      Photein::Logger.info('conflicting timestamp found; adding counter to existing file')
      FileUtils.mv(Dir[collision_glob].first, collision_glob.sub('*', 'a'))
    end
  else # TODO: if multiple files found, rectify them?
  end

  # return the next usable counter
  Dir[collision_glob].max&.slice(/.(?=#{Regexp.escape(collision_glob.extname)})/)&.next
    .tap { |counter| raise 'Unresolved timestamp conflict' unless [*Array('a'..'z'), nil].include?(counter) }
end
tempfile() click to toggle source
# File lib/photein/media_file.rb, line 79
def tempfile
  Pathname(Dir.tmpdir).join('photein')
    .tap(&FileUtils.method(:mkdir_p))
    .join(dest_path.basename)
end
timestamp() click to toggle source
# File lib/photein/media_file.rb, line 94
def timestamp
  @timestamp ||= (metadata_stamp || filename_stamp)
end