# coding: utf-8
def wprintf(format, *args)
  STDERR.printf("pdumpfs: " + format + "\n", *args)
end

#https://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
# Cross-platform way of finding an executable in the $PATH.
#
#   which('ruby') #=> /usr/bin/ruby
def which(cmd)
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each do |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable?(exe) && !File.directory?(exe)
    end
  end
  nil
end

class File
  def self.real_file?(path)
    FileTest.file?(path) and not FileTest.symlink?(path)
  end

  def self.anything_exist?(path)
    FileTest.exist?(path) or FileTest.symlink?(path)
  end

  def self.real_directory?(path)
    FileTest.directory?(path) and not FileTest.symlink?(path)
  end

  def self.force_symlink(src, dest)
    begin
      File.unlink(dest) if File.anything_exist?(dest)
      File.symlink(src, dest)
    rescue 
    end
  end

  def self.force_link(src, dest)
    File.unlink(dest) if File.anything_exist?(dest)
    File.link(src, dest)
  end

  def self.readable_file?(path)
    FileTest.file?(path) and FileTest.readable?(path)
  end

  def self.split_all(path)
    parts = []
    while true
      dirname, basename = File.split(path)
      break if path == dirname
      parts.unshift(basename) unless basename == "."
      path = dirname
    end
    return parts
  end
end


module QdumpfsFind
  def find(logger, *paths)
    block_given? or return enum_for(__method__, *paths)
    paths.collect!{|d|
      raise Errno::ENOENT unless File.exist?(d);
      d.dup
    }
    while file = paths.shift
      catch(:prune) do
        yield file.dup.taint
        begin
          s = File.lstat(file)
        rescue => e
          logger.print("File.lstat path=#{file} error=#{e.message}")
          next
        end
        if s.directory? then
          begin
            fs = Dir.entries(file, :encoding=>'UTF-8')
          rescue => e
            logger.print("Dir.entries path=#{file} error=#{e.message}")
            next
          end
          fs.sort!
          fs.reverse_each {|f|
            next if f == "." or f == ".."
            f = File.join(file, f)
            paths.unshift f.untaint
          }
        end
      end
    end
  end
  
  def prune
    throw :prune
  end
  
  module_function :find, :prune
end


module QdumpfsUtils

  # We don't use File.copy for calling @interval_proc.
  def copy_file(src, dest)
    begin    
      File.open(src, 'rb') {|r|
        File.open(dest, 'wb') {|w|
          block_size = (r.stat.blksize or 8192)
          i = 0
          while true
            block = r.sysread(block_size)
            w.syswrite(block)
            i += 1
            @written_bytes += block.size
          end
        }
      }
    rescue EOFError => e
      # puts e.message, e.backtrace
    end    
    unless FileTest.file?(dest)
      raise "copy_file fails #{dest}"
    end
  end 
  
  # incomplete substitute for cp -p
  def copy(src, dest)
    stat = File.stat(src)
    copy_file(src, dest)
    File.chmod(0200, dest) if windows?
    File.utime(stat.atime, stat.mtime, dest)
    File.chmod(stat.mode, dest) # not necessary. just to make sure
  end
  
  def convert_bytes(bytes)
    if bytes < 1024
      sprintf("%dB", bytes)
    elsif bytes < 1024 * 1000 # 1000kb
      sprintf("%.1fKB", bytes.to_f / 1024)
    elsif bytes < 1024 * 1024 * 1000  # 1000mb
      sprintf("%.1fMB", bytes.to_f / 1024 / 1024)
    else
      sprintf("%.1fGB", bytes.to_f / 1024 / 1024 / 1024)
    end
  end
  
  def same_file?(f1, f2)
    #    File.real_file?(f1) and File.real_file?(f2) and
    #    File.size(f1) == File.size(f2) and File.mtime(f1) == File.mtime(f2)
    real_file = File.real_file?(f1) and File.real_file?(f2)
    same_size = File.size(f1) == File.size(f2)
    
    #    mtime1 = File.mtime(f1).strftime('%F %T.%N')
    #    mtime2 = File.mtime(f2).strftime('%F %T.%N')
    same_mtime = File.mtime(f1).to_i == File.mtime(f2).to_i
    #    p "#{real_file} #{same_size} #{same_mtime}(#{mtime1}<=>#{mtime2})"
    real_file and same_size and same_mtime
  end
  
  def detect_type(src, latest = nil)
    type = "unsupported"
    if File.real_directory?(src)
      type = "directory"
    else
      if latest and File.real_file?(latest)
        case File.ftype(src)
        when "file"
          same_file = same_file?(src, latest)
          #          p "same_file? #{src} #{latest} result=#{same_file}"
          if same_file
            type = "unchanged"
          else
            type = "updated"
          end
        when "link"
          # the latest backup file is a real file but the
          # current source file is changed to symlink.
          type = "symlink"
        end
      else
        case File.ftype(src)
        when "file"
          type = "new_file"
        when "link"
          type = "symlink"
          end
      end
    end
    return type
  end
  
  def fmt(time)
    time.strftime('%Y/%m/%d %H:%M:%S')
  end
  
  def chown_if_root(type, src, today)
    return unless Process.uid == 0 and type != "unsupported"
    if type == "symlink"
      if File.respond_to?(:lchown)
        stat = File.lstat(src)
        File.lchown(stat.uid, stat.gid, today)
      end
    else
      stat = File.stat(src)
      File.chown(stat.uid, stat.gid, today)
    end
  end
  
  def restore_dir_attributes(dirs)
    dirs.each {|dir, stat|
      File.utime(stat.atime, stat.mtime, dir)
      File.chmod(stat.mode, dir)
    }
  end    
  
  def make_relative_path(path, base)
    pattern = sprintf("^%s%s?", Regexp.quote(base), File::SEPARATOR)
    path.sub(Regexp.new(pattern), "")
  end
  
  def fputs(file, msg)
    puts msg
    file.puts msg
  end
  
  def time_diff(start_time, end_time)
    seconds_diff = (start_time - end_time).to_i.abs
    
    hours = seconds_diff / 3600
    seconds_diff -= hours * 3600
    
    minutes = seconds_diff / 60
    seconds_diff -= minutes * 60
    
    seconds = seconds_diff
    
    '%02d:%02d:%02d' % [hours, minutes, seconds]
  end
  
  def create_latest_symlink(dest, today)
    # 最新のバックアップに"latest"というシンボリックリンクをはる(Windowsだと動かない)
    latest_day = File.dirname(make_relative_path(today, dest))
      latest_symlink = File.join(dest, "latest")
    #      puts "force_symlink #{latest_day} #{latest_symlink}"
    File.force_symlink(latest_day, latest_symlink)
  end
  
  def same_directory?(src, dest)
    src  = File.expand_path(src)
    dest = File.expand_path(dest)
    return src == dest
  end
  
  def sub_directory?(src, dest)
    src  = File.expand_path(src)
    dest = File.expand_path(dest)
    src  += File::SEPARATOR unless /#{File::SEPARATOR}$/.match(src)
    return /^#{Regexp.quote(src)}/.match(dest)
  end
  
  def datedir(date)
    s = File::SEPARATOR
    sprintf "%d%s%02d%s%02d", date.year, s, date.month, s, date.day
  end
  
  def past_date?(year, month, day, t)
    ([year, month, day] <=> [t.year, t.month, t.day]) < 0
  end
  
  def to_win_path(path)
    path.gsub(/\//, '\\')
  end
  
  def to_unix_path(path)
    path.gsub(/\\/, '/')
  end

  def to_time(date)
    Time.local(date.year, date.month, date.day)
  end
end