#
# fileutils.rb
#
#   Copyright (c) 2000 Minero Aoki
#
#   This program is free software.
#   You can distribute/modify this program under the terms of
#   the GNU General Public License version 2 or later.
#

module FileUtilities

  extend FileTest

  Separator = File::ALT_SEPARATOR || File::SEPARATOR


  module_function


  def join( *args )
    args.flatten!
    args.join( Separator )
  end


  def cd( dn )
    if iterator? then
      pre = Dir.pwd
      Dir.chdir dn
      yield
      Dir.chdir pre
    else
      Dir.chdir dn
    end
  end
  alias chdir cd

  def is_newer?( new, old )
    return false unless exist? new
    File.ctime(new) >= File.ctime(old)
  end
  alias uptodate? is_newer?

  def is_older?( old, new )
    return true unless exist? old
    File.ctime(new) <= File.ctime(old)
  end


  def mkdir( dn )
    return if directory? dn
    Dir.mkdir dn
  end

  def mkdir_p( dn )
    return if directory? dn
    mkdir_p File.dirname dn
    Dir.mkdir dn unless File.basename(dn) == ''
  end


  def ln( old, new )
    dest = fu_make_dest_fname( old, new )
    File.link old, dest
  end

  def ln_s( old, new )
    dest = fu_make_dest_fname( old, new )
    File.symlink old, new
  end


  BSIZE = 1024 * 2  # 2KB

  def cp( from, to )
    dest = fu_make_dest_fname( from, to )

    rf = File.open( from )     ; rf.binmode
    wf = File.open( dest, 'w' ); wf.binmode
    s = nil

    begin
      while s = rf.sysread( BSIZE * 16 ) do
        wf.syswrite s
      end
    rescue EOFError
    ensure
      rf.close
      wf.close
    end
  end

  def cp_r( from, to )
    if directory? from then
      from = File.expand_path(from)
      do_cp_r File.dirname(from), File.basename(from), to
    else
      cp from, to
    end
  end

  def do_cp_r( base, abs, to )
    dirs = nil
    Dir.open( join base, abs ) {|d| dirs = d.to_a }
    dirs.each do |fn|
      if directory? fn then
        next if /\A\.\.?\z/o === fn
        mkdir join(to, abs, fn)
        do_cp_r base, join(abs, fn), to
      else
        cp join(base, abs, fn), join(to, abs, fn)
      end
    end
  end
  private_class_method :do_cp_r


  def mv( from, to )
    dest = fu_make_dest_fname( from, to )
    st = File.stat( from )
    if File::ALT_SEPARATOR then
      File.unlink dest
    end

    begin
      File.rename from, dest
    rescue
      if symlink? from then
        File.symlink File.readlink(from), dest
        File.unlink from
      else
        cp from, dest
        File.unlink from
        File.utime st.atime, st.mtime, dest
        begin
          File.chown st.uid, st.gid, dest
        rescue
        end
      end
    end
  end


  def rm( fn )
    File.unlink fn
  end

  def rm_f( fn )
    return unless exist? fn

    unless file? fn then
      futilsmsg "'rm -f #{fn}' fail: not file"
      return
    end
    File.chmod 0777, fn
    File.unlink fn
  end

  def rm_rf( *args )
    args.flatten!
    args.each do |i|
      if directory? i then
        do_rmrf i
      else
        rm_f i
      end
    end
  end

  def do_rmrf( dn )
    Dir.foreach( dn ) do |fn|
      next if /\A\.\.?\z/ === fn
      fn = join( dn, fn )
      if directory? fn then
        do_rmrf fn
      else
        rm_f fn
      end
    end
    Dir.rmdir dn
  end
  private_class_method :do_rmrf


  def cmp( filea, fileb )
    a = File.open( filea ); a.binmode
    b = File.open( fileb ); b.binmode

    stra = strb = ''
    begin
      while stra == strb do
        stra = a.read( BSIZE )
        strb = b.read( BSIZE )
        unless stra and strb then
          if stra.nil? and strb.nil? then
            return true
          end
        end
      end
    rescue EOFError
      ;
    ensure
      a.close
      b.close
    end

    false
  end


  def install( from, to, mode = nil )
    dest = fu_make_dest_fname( from, to )
    unless exist? dest and cmp( from, dest ) then
      rm_f dest
      cp from, dest
      File.chmod mode, dest if mode
    end
  end


  def fu_make_dest_fname( from, to )
    if directory? to then
      (to[-1,1] == Separator ? to : to + Separator) + File.basename(from)
    else
      to
    end
  end
  private_class_method :fu_make_dest_fname

end
