Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / perl / CXReplicateDir.pm

# (c) Copyright 2002-2008, 2010. CodeWeavers, Inc.
package CXReplicateDir;
use warnings;
use strict;

use CXLog;
use CXUtils;


#
# Functions for building and saving file lists
#

sub scan_tree($;$)
{
    my ($rootdir, $re_skip)=@_;
    $rootdir.="/" if ($rootdir !~ m%/$%);
    my $types={};
    my $timestamps={};
    my $links={};

    cxlog("\nScanning '$rootdir':\n");
    my @dirs=("");
    while (@dirs)
    {
        my $dh;
        my $dir=shift @dirs;
        cxlog("$dir\n") if ($dir ne "");
        if (!opendir($dh, "$rootdir$dir"))
        {
            cxerr("unable to read '$rootdir$dir': $!\n");
            return 1;
        }
        foreach my $dentry (readdir $dh)
        {
            next if ($dentry =~ /^\.\.?$/);
            $dentry="$dir$dentry";

            if (defined $re_skip and $dentry =~ /$re_skip/)
            {
                cxlog("skipping '$dentry'\n");
                next;
            }

            my @st=stat("$rootdir$dentry");
            if (@st)
            {
                if (-d _)
                {
                    if (-l "$rootdir$dentry")
                    {
                        my $link=readlink("$rootdir$dentry");
                        $types->{$dentry}="l->d";
                        $links->{$dentry}=$link;
                    }
                    else
                    {
                        $types->{$dentry}="d";
                        push @dirs, "$dentry/";
                    }
                }
                elsif (-f _ or -b _)
                {
                    $timestamps->{$dentry}=$st[9];
                    if (-l "$rootdir$dentry")
                    {
                        my $link=readlink("$rootdir$dentry");
                        $types->{$dentry}="l->f";
                        $links->{$dentry}=$link;
                    }
                    else
                    {
                        $types->{$dentry}="f";
                    }
                }
                else
                {
                    cxlog("ignoring '$dentry' (not a file or directory)\n");
                }
            }
            else
            {
                cxwarn("ignoring dead link '$rootdir$dentry'\n");
            }
        }
        closedir($dh);
    }
    return { types => $types,
             timestamps => $timestamps,
             links => $links
           };
}

sub read_file_list($)
{
    my ($file)=@_;
    my $types={};
    my $links={};

    my $in;
    if (!open($in, "<", $file))
    {
        cxerr("unable to open '$file' for reading: $!\n");
    }
    else
    {
        while (<$in>)
        {
            if (/^(\S+) (.*)$/)
            {
                my ($type, $entry)=($1, $2);
                $entry =~ s%/$%%;
                if ($type eq "d")
                {
                    $types->{$entry}="d";
                }
                elsif ($type =~ s/^l->d=//)
                {
                    $types->{$entry}="l->d";
                    $links->{$entry}=demangle_string($type);
                }
                elsif ($type =~ s/^l->f=//)
                {
                    $types->{$entry}="l->f";
                    $links->{$entry}=demangle_string($type);
                }
                else
                {
                    $types->{$entry}="f";
                    # We don't care about the timestamp for old files
                }
            }
        }
        close($in);
    }
    return { types => $types,
             links => $links
           };
}

sub write_file_list($$)
{
    my ($file, $list)=@_;

    my $out;
    if (!open($out, ">", $file))
    {
        cxerr("unable to open '$file' for writing: $!\n");
        return;
    }
    my $types=$list->{types};
    foreach my $entry (sort keys %$types)
    {
        my $type=$types->{$entry};
        if ($type eq "f")
        {
            print $out "$list->{timestamps}->{$entry} $entry\n";
        }
        elsif ($type eq "d")
        {
            print $out "d $entry\n";
        }
        else
        {
            print $out "$type=", mangle_string($list->{links}->{$entry}), " $entry\n";
        }
    }
    close($out);
}


#
# CXReplicateDir initialization
#

sub new($$$)
{
    my ($class, $refdir, $dstdir)=@_;
    my $self={ refdir => $refdir,
               dstdir => $dstdir };
    bless $self, $class;
    return $self;
}

sub set_link_type($$)
{
    my ($self, $linktype)=@_;
    if (!defined $linktype)
    {
        $linktype="symbolic";
    }
    else
    {
        $linktype =~ s/\s*//;
        $linktype =~ tr/A-Z/a-z/;
    }

    if ($linktype eq "symbolic" or $linktype eq "symbolic,hard")
    {
        $self->{dolink}=\&do_link_symbolic;
    }
    elsif ($linktype eq "hard")
    {
        $self->{dolink}=\&do_link_hard;
    }
    elsif ($linktype eq "hard,symbolic")
    {
        $self->{dolink}=\&do_link_hard_symbolic;
    }
    else
    {
        cxwarn("ignoring invalid LinkType specification '$linktype'\n");
        $self->{dolink}=\&do_link_symbolic;
    }
}

sub set_full_copy($$)
{
    my ($self, $full_copy)=@_;
    $self->{full_copy}=$full_copy;
}

sub set_policy_settings($$$)
{
    my ($self, $section, $validate)=@_;

    if ($section)
    {
        cxlog("Policy list:\n");
        foreach my $regexp (@{$section->get_field_list()})
        {
            my $policy=$section->get($regexp);
            $policy =~ s/\s*//;
            $policy =~ tr/A-Z/a-z/;
            if ($validate)
            {
                # Check that the policy is valid
                my $count=0;
                my $delignore=0;
                foreach my $flag (split /,/, $policy)
                {
                    if ($flag =~ /^(?:delete|ignore)$/)
                    {
                        $count++;
                        $delignore|=1;
                    }
                    elsif ($flag =~ /^(?:link|linkdir|copy|registry)$/)
                    {
                        $count++;
                    }
                    elsif ($flag !~ /^(?:replacefiles|replacedirs|replaceconflicting)$/)
                    {
                        cxwarn("ignoring invalid policy '$policy'\n");
                        $policy=undef;
                        last;
                    }
                    else
                    {
                        $delignore|=2;
                    }
                }
                if ($count != 1 or $delignore == 3)
                {
                    cxwarn("ignoring invalid policy '$policy'\n");
                    $policy=undef;
                }
            }
            if ($policy)
            {
                cxlog("  $regexp -> $policy\n");
                push @{$self->{policy_list}}, $regexp if ($regexp ne ".*");
                $self->{policies}->{$regexp}=$policy;
            }
        }
    }
    $self->{default_policy}=$self->{policies}->{".*"} || "link";
    delete $self->{policies}->{".*"};
}


#
# Merge actions
#

sub action_noop($$)
{
    return "recurse";
}

sub action_skip($$)
{
    # Skip the sub-directories
    return "skip";
}

sub action_cp($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_cp}}, $entry;
    return "recurse";
}

sub action_cpr($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_mkdir}}, $entry;
    # Return recurse so the recursive copy will simply happen
    # as a side-effect of the caller's loop.
    return "recurse";
}

sub action_ln($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_ln}}, $entry;
    return "recurse";
}

sub action_lns($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_lns}}, $entry;
    # Skip the sub-directories
    return "skip";
}

sub action_cpln($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_cpln}}, [$entry, $cxreplicate->{new}->{links}->{$entry}];
    return "recurse";
}

sub action_regoverride($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_regoverride}}, $entry;
    return "recurse";
}

sub action_regpreserve($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_regpreserve}}, $entry;
    return "recurse";
}

sub action_ifeq_rm($$)
{
    my ($cxreplicate, $entry)=@_;
    my $old=$cxreplicate->{old}->{links}->{$entry};
    if (defined $old and $old eq $cxreplicate->{link})
    {
        push @{$cxreplicate->{to_rm}}, $entry;
    }
    return "recurse";
}

sub action_ifne_cpln($$)
{
    my ($cxreplicate, $entry)=@_;
    my $link=$cxreplicate->{new}->{links}->{$entry};
    if ($link ne $cxreplicate->{link})
    {
        push @{$cxreplicate->{to_rm}}, $entry;
        push @{$cxreplicate->{to_cpln}}, [$entry, $link];
    }
    return "recurse";
}

sub action_chk_cpln($$)
{
    my ($cxreplicate, $entry)=@_;
    my $new=$cxreplicate->{new}->{links}->{$entry};
    if ($cxreplicate->{link} ne $new)
    {
        my $old=$cxreplicate->{old}->{links}->{$entry};
        if (defined $old and $cxreplicate->{link} eq $old)
        {
            push @{$cxreplicate->{to_rm}}, $entry;
            push @{$cxreplicate->{to_cpln}}, [$entry, $new];
        }
    }
    return "recurse";
}

sub action_rm($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rm}}, $entry;
    return "recurse";
}

sub action_rm_cp($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rm}}, $entry;
    push @{$cxreplicate->{to_cp}}, $entry;
    return "recurse";
}

sub action_rm_ln($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rm}}, $entry;
    push @{$cxreplicate->{to_ln}}, $entry;
    return "recurse";
}

sub action_rm_cpln($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rm}}, $entry;
    push @{$cxreplicate->{to_cpln}}, [$entry, $cxreplicate->{new}->{links}->{$entry}];
    return "recurse";
}

sub action_rm_new($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rm}}, $entry;
    while (my ($old_entry, $old_type) = each %{$cxreplicate->{old}->{types}})
    {
        delete $cxreplicate->{old}->{types}->{$old_entry} if index($old_entry, $entry) == 0;
    }
    # Return 'ignore' so this entry will not be put in
    # the 'stub' list, and so we skip the sub-directories.
    return "ignore";
}

sub action_rmdir($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rmdir}}, $entry;
    return "recurse";
}

sub action_rmrf($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rmtree}}, $entry;
    # Skip the sub-directories
    return "skip";
}

sub action_rmrf_new($$)
{
    my ($cxreplicate, $entry)=@_;
    push @{$cxreplicate->{to_rmtree}}, $entry;
    delete $cxreplicate->{old}->{types}->{$entry};
    # Return 'ignore' so this entry will not be put in
    # the 'stub' list, and so we skip the sub-directories.
    return "ignore";
}

sub action_update($$)
{
    my ($cxreplicate, $entry)=@_;
    if ($cxreplicate->{stamp} < $cxreplicate->{new}->{timestamps}->{$entry})
    {
        push @{$cxreplicate->{to_cp}}, $entry;
    }
    return "recurse";
}


#
# File merges
#

sub get_file_merge_column($)
{
    my ($policy)=@_;
    if ($policy =~ /link/)
    {
        return "link+replacefiles" if ($policy =~ /replacefiles/);
        return "link";
    }
    elsif ($policy =~ /registry/)
    {
        return "registry+replacefiles" if ($policy =~ /replacefiles/);
        return "registry";
    }
    # else $policy =~ /copy/
    return "copy+replacefiles" if ($policy =~ /replacefiles/);
    return "copy";
}

my $file_merge_n_n_f=
{"link"                  => \&action_ln,
 "link+replacefiles"     => \&action_ln,
 "copy"                  => \&action_cp,
 "copy+replacefiles"     => \&action_cp,
 "registry"              => \&action_cp,
 "registry+replacefiles" => \&action_cp
};
my $file_merge_e_n_f=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_ln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_cp,
 "registry"              => \&action_cp,
 "registry+replacefiles" => \&action_cp
};
my $file_merge_f_f=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_rm_ln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_update,
 "registry"              => \&action_regpreserve,
 "registry+replacefiles" => \&action_regoverride
};
my $file_merge_lf_f=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_rm_ln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_rm_cp,
 "registry"              => \&action_rm_cp,
 "registry+replacefiles" => \&action_rm_cp
};
my $file_merge_lmf_f=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_noop,
 "copy"                  => \&action_rm_cp,
 "copy+replacefiles"     => \&action_rm_cp,
 "registry"              => \&action_rm_cp,
 "registry+replacefiles" => \&action_rm_cp
};
my $file_merge_ln_f=
{"link"                  => \&action_rm_ln,
 "link+replacefiles"     => \&action_rm_ln,
 "copy"                  => \&action_rm_cp,
 "copy+replacefiles"     => \&action_rm_cp,
 "registry"              => \&action_rm_cp,
 "registry+replacefiles" => \&action_rm_cp
};
my $file_merge_n_f_n=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_noop,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_noop,
 "registry"              => \&action_noop,
 "registry+replacefiles" => \&action_noop
};
my $file_merge_n_ln_n=
{"link"                  => \&action_rm,
 "link+replacefiles"     => \&action_rm,
 "copy"                  => \&action_rm,
 "copy+replacefiles"     => \&action_rm,
 "registry"              => \&action_rm,
 "registry+replacefiles" => \&action_rm
};
my $file_merge_e_f_n=
{"link"                  => \&action_rm,
 "link+replacefiles"     => \&action_rm,
 "copy"                  => \&action_rm,
 "copy+replacefiles"     => \&action_rm,
 "registry"              => \&action_rm,
 "registry+replacefiles" => \&action_rm
};
my $file_merge_e_lf_n=
{"link"                  => \&action_ifeq_rm,
 "link+replacefiles"     => \&action_rm,
 "copy"                  => \&action_ifeq_rm,
 "copy+replacefiles"     => \&action_rm,
 "registry"              => \&action_rm,
 "registry+replacefiles" => \&action_rm
};
my $file_merge={         "-;-;f"        => $file_merge_n_n_f,
                         "e;-;f"        => $file_merge_e_n_f,
                         "-;f;f"        => $file_merge_f_f,
                         "e;f;f"        => $file_merge_f_f,
                         "-;l->f;f"     => $file_merge_lf_f,
                         "e;l->f;f"     => $file_merge_lf_f,
                         "-;l->mf;f"    => $file_merge_lmf_f,
                         "e;l->mf;f"    => $file_merge_lmf_f,
                         "-;l->-;f"     => $file_merge_ln_f,
                         "e;l->-;f"     => $file_merge_ln_f,
                         "-;f;-"        => $file_merge_n_f_n,
                         "-;l->f;-"     => $file_merge_n_f_n,
                         "-;l->-;-"     => $file_merge_n_ln_n,
                         "e;f;-"        => $file_merge_e_f_n,
                         "e;l->-;-"     => $file_merge_e_f_n,
                         "e;l->f;-"     => $file_merge_e_lf_n
               };


#
# File Symbolic Link merges
#

sub get_linkfile_merge_column($)
{
    return get_file_merge_column($_[0]);
}

my $linkfile_merge_n_n_lf=
{"link"                  => \&action_cpln,
 "link+replacefiles"     => \&action_cpln,
 "copy"                  => \&action_cp,
 "copy+replacefiles"     => \&action_cp,
 "registry"              => \&action_cp,
 "registry+replacefiles" => \&action_cp
};
my $linkfile_merge_e_n_lf=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_cpln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_cp,
 "registry"              => \&action_cp,
 "registry+replacefiles" => \&action_cp
};
my $linkfile_merge_f_lf=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_rm_cpln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_update,
 "registry"              => \&action_regpreserve,
 "registry+replacefiles" => \&action_regoverride
};
my $linkfile_merge_n_lf_lf=
{"link"                  => \&action_noop,
 "link+replacefiles"     => \&action_ifne_cpln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_rm_cp,
 "registry"              => \&action_rm_cp,
 "registry+replacefiles" => \&action_rm_cp
};
my $linkfile_merge_e_lf_lf=
{"link"                  => \&action_chk_cpln,
 "link+replacefiles"     => \&action_ifne_cpln,
 "copy"                  => \&action_noop,
 "copy+replacefiles"     => \&action_rm_cp,
 "registry"              => \&action_rm_cp,
 "registry+replacefiles" => \&action_rm_cp
};
my $linkfile_merge_lmf_lf=
{"link"                  => \&action_rm_cpln,
 "link+replacefiles"     => \&action_rm_cpln,
 "copy"                  => \&action_rm_cp,
 "copy+replacefiles"     => \&action_rm_cp,
 "registry"              => \&action_rm_cp,
 "registry+replacefiles" => \&action_rm_cp
};
my $linkfile_merge={     "-;-;l->f"     => $linkfile_merge_n_n_lf,
                         "e;-;l->f"     => $linkfile_merge_e_n_lf,
                         "-;f;l->f"     => $linkfile_merge_f_lf,
                         "e;f;l->f"     => $linkfile_merge_f_lf,
                         "-;l->f;l->f"  => $linkfile_merge_n_lf_lf,
                         "e;l->f;l->f"  => $linkfile_merge_e_lf_lf,
                         "-;l->mf;l->f" => $linkfile_merge_lmf_lf,
                         "e;l->mf;l->f" => $linkfile_merge_lmf_lf,
                         "-;l->-;l->f"  => $linkfile_merge_lmf_lf,
                         "e;l->-;l->f"  => $linkfile_merge_lmf_lf
                   };


#
# Directory merges
#

sub get_directory_merge_column($)
{
    my ($policy)=@_;
    if ($policy =~ /linkdir/)
    {
        return "linkdir+replacedirs" if ($policy =~ /replacedirs/);
        return "linkdir";
    }
    # else $policy =~ /(link|copy|registry)/
    return "link+replacedirs" if ($policy =~ /replacedirs/);
    return "link";
}

my $directory_merge_n_n_d=
{"link"                  => \&action_cpr,
 "link+replacedirs"      => \&action_cpr,
 "linkdir"               => \&action_lns,
 "linkdir+replacedirs"   => \&action_lns
};
my $directory_merge_e_n_d=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_cpr,
 "linkdir"               => \&action_noop,
 "linkdir+replacedirs"   => \&action_lns
};
my $directory_merge_d_d=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_noop,
 "linkdir"               => \&action_noop,
 "linkdir+replacedirs"   => \&action_rmrf_new
};
my $directory_merge_ld_d=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_rm_new,
 "linkdir"               => \&action_noop,
 "linkdir+replacedirs"   => \&action_rm_new
};
my $directory_merge_lmd_d=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_rm_new,
 "linkdir"               => \&action_noop,
 "linkdir+replacedirs"   => \&action_noop
};
my $directory_merge_ln_d=
{"link"                  => \&action_rm_new,
 "link+replacedirs"      => \&action_rm_new,
 "linkdir"               => \&action_rm_new,
 "linkdir+replacedirs"   => \&action_rm_new
};
my $directory_merge_n_d_n=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_noop,
 "linkdir"               => \&action_noop,
 "linkdir+replacedirs"   => \&action_noop
};
my $directory_merge_n_ld_n=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_noop,
 "linkdir"               => \&action_noop,
 "linkdir+replacedirs"   => \&action_noop
};
my $directory_merge_e_d_n=
{"link"                  => \&action_rmdir,
 "link+replacedirs"      => \&action_rmrf,
 "linkdir"               => \&action_rmrf,
 "linkdir+replacedirs"   => \&action_rmrf
};
my $directory_merge_e_ld_n=
{"link"                  => \&action_ifeq_rm,
 "link+replacedirs"      => \&action_rm,
 "linkdir"               => \&action_ifeq_rm,
 "linkdir+replacedirs"   => \&action_rm
};
my $directory_merge={    "-;-;d"        => $directory_merge_n_n_d,
                         "e;-;d"        => $directory_merge_e_n_d,
                         "-;d;d"        => $directory_merge_d_d,
                         "e;d;d"        => $directory_merge_d_d,
                         "-;l->d;d"     => $directory_merge_ld_d,
                         "e;l->d;d"     => $directory_merge_ld_d,
                         "-;l->md;d"    => $directory_merge_lmd_d,
                         "e;l->md;d"    => $directory_merge_lmd_d,
                         "-;l->-;d"     => $directory_merge_ln_d,
                         "e;l->-;d"     => $directory_merge_ln_d,
                         "-;d;-"        => $directory_merge_n_d_n,
                         "-;l->d;-"     => $directory_merge_n_ld_n,
                         "e;d;-"        => $directory_merge_e_d_n,
                         "e;l->d;-"     => $directory_merge_e_ld_n
                    };


#
# Directory Symbolic Link merges
#

sub get_linkdir_merge_column($)
{
    my ($policy)=@_;
    return "link+replacedirs" if ($policy =~ /replacedirs/);
    return "link";
}

my $linkdir_merge_n_n_ld=
{"link"                  => \&action_cpln,
 "link+replacedirs"      => \&action_cpln
};
my $linkdir_merge_e_n_ld=
{"link"                  => \&action_noop,
 "link+replacedirs"      => \&action_cpln
};
my $linkdir_merge_d_ld=
{"link"                  => \&action_skip,
 "link+replacedirs"      => \&action_rmrf_new
};
my $linkdir_merge_ld_ld=
{"link"                  => \&action_chk_cpln,
 "link+replacedirs"      => \&action_chk_cpln
};
my $linkdir_merge_lmd_ld=
{"link"                  => \&action_rm_cpln,
 "link+replacedirs"      => \&action_rm_cpln
};
my $linkdir_merge={      "-;-;l->d"     => $linkdir_merge_n_n_ld,
                         "e;-;l->d"     => $linkdir_merge_e_n_ld,
                         "-;d;l->d"     => $linkdir_merge_d_ld,
                         "e;d;l->d"     => $linkdir_merge_d_ld,
                         "-;l->d;l->d"  => $linkdir_merge_ld_ld,
                         "e;l->d;l->d"  => $linkdir_merge_ld_ld,
                         "-;l->md;l->d" => $linkdir_merge_lmd_ld,
                         "e;l->md;l->d" => $linkdir_merge_lmd_ld,
                         "-;l->-;l->d"  => $linkdir_merge_lmd_ld,
                         "e;l->-;l->d"  => $linkdir_merge_lmd_ld
                    };


#
# Conflict merges
#

sub get_conflict_merge_column($)
{
    my ($policy)=@_;
    return ($policy =~ /replaceconflicting/ ? "replaceconflicting" : "link");
}

my $conflict_merge_f_d=
{"link"                  => \&action_noop,
 "replaceconflicting"    => \&action_rm_new
};
my $conflict_merge_d_f=
{"link"                  => \&action_skip,
 "replaceconflicting"    => \&action_rmrf_new
};
my $conflict_merge_ld_f=
{"link"                  => \&action_noop,
 "replaceconflicting"    => \&action_rm_new
};
my $conflict_merge={     "-;f;d"        => $conflict_merge_f_d,
                         "e;f;d"        => $conflict_merge_f_d,
                         "-;l->f;d"     => $conflict_merge_f_d,
                         "e;l->f;d"     => $conflict_merge_f_d,
                         "-;f;l->d"     => $conflict_merge_f_d,
                         "e;f;l->d"     => $conflict_merge_f_d,
                         "-;l->f;l->d"  => $conflict_merge_f_d,
                         "e;l->f;l->d"  => $conflict_merge_f_d,
                         "-;d;f"        => $conflict_merge_d_f,
                         "e;d;f"        => $conflict_merge_d_f,
                         "-;d;l->f"     => $conflict_merge_d_f,
                         "e;d;l->f"     => $conflict_merge_d_f,
                         "-;l->d;f"     => $conflict_merge_ld_f,
                         "e;l->d;f"     => $conflict_merge_ld_f,
                         "-;l->d;l->f"  => $conflict_merge_ld_f,
                         "e;l->d;l->f"  => $conflict_merge_ld_f
                    };


#
# The merge engine
#

sub merge_tree_get_policy($$)
{
    my ($self, $entry)=@_;
    foreach my $regexp (@{$self->{policy_list}})
    {
        return $self->{policies}->{$regexp} if ($entry =~ /$regexp/i);
    }
    return $self->{default_policy};
}

sub merge_tree($;$)
{
    my ($self, $re_skip)=@_;
    my $dstdir=$self->{dstdir};
    $dstdir.="/" if ($dstdir !~ m%/$%);
    my $refdir=$self->{refdir};
    $refdir.="/" if ($refdir !~ m%/$%);

    my $old=$self->{old}->{types};
    my $new=$self->{new}->{types};
    my $stub;

    # Merge existing files
    cxlog("\nMerging files in '$dstdir':\n");
    my @dirs=("");
    while (@dirs)
    {
        my $dh;
        my $dir=shift @dirs;
        if (!opendir($dh, "$dstdir$dir"))
        {
            cxerr("unable to read '$dstdir$dir': $!\n");
            return 1;
        }
        foreach my $dentry (readdir $dh)
        {
            next if ($dentry =~ /^\.\.?$/);
            $dentry="$dir$dentry";
            next if (defined $re_skip and $dentry =~ /$re_skip/);

            # Determine which policy applies to this entry
            my $policy=$self->merge_tree_get_policy($dentry);

            # Handle the 'delete' and 'ignore' policies
            if ($policy eq "delete")
            {
                if (-d "$dstdir$dentry")
                {
                    cxlog("$dentry\t(d) delete\n");
                    push @{$self->{to_rmtree}}, $dentry;
                }
                else
                {
                    cxlog("$dentry\t(f) delete\n");
                    push @{$self->{to_rm}}, $dentry;
                }
                $stub->{$dentry}="delete";
                next;
            }
            if ($policy eq "ignore")
            {
                cxlog("$dentry\t(?) ignore\n");
                $stub->{$dentry}="ignore";
                next;
            }

            # Determine what's the type/timestamp of this entry
            my $type;
            my @st=stat("$dstdir$dentry");
            if (@st)
            {
                if (-f _)
                {
                    $type="f";
                }
                elsif (-d _)
                {
                    $type="d";
                }
                else
                {
                    cxwarn("ignoring special file '$dstdir$dentry'\n");
                    $stub->{$dentry}="special";
                    next;
                }
                if (-l "$dstdir$dentry")
                {
                    $self->{link}=readlink("$dstdir$dentry");
                    if ($self->{link} ne "$refdir$dentry")
                    {
                        if ($self->{link} =~ m%\Q$refdir\Efake_windows/%)
                        {
                            # Force the old links to be recreated
                            $type="l->-";
                        }
                        else
                        {
                            $type="l->$type";
                        }
                    }
                    else
                    {
                        if (!exists $new->{$dentry})
                        {
                            # Force the links to be recreated if they
                            # only work on a case-insensitive filesystem.
                            $type="l->-";
                        }
                        else
                        {
                            $type="l->m$type";
                        }
                    }
                }
                else
                {
                    $self->{stamp}=$st[9];
                }
            }
            else
            {
                $type="l->-";
            }

            # If doing a full copy then tweak the policy here
            # so that the user ends up with a standalone copy
            if ($self->{full_copy})
            {
                if ($type =~ /^l->m[fd]$/)
                {
                    $policy="copy,replacedirs,replacefiles";
                }
                elsif ($policy =~ /(?:link|linkdir)/)
                {
                    $policy =~ s/replace(?:files|dirs),//g;
                    $policy =~ s/,replace(?:files|dirs)//g;
                }
            }

            # Determine which merge action to take
            my ($table, $line, $column);
            $line=join(";",
                       (exists $old->{$dentry} ? "e" : "-"),
                       $type,
                       (exists $new->{$dentry} ? $new->{$dentry} : "-"));

            if ($type =~ /d/)
            {
                if (!defined $new->{$dentry} or $new->{$dentry} eq "d")
                {
                    $table=$directory_merge;
                    $column=get_directory_merge_column($policy);
                }
                elsif (defined $new->{$dentry} and $new->{$dentry} eq "l->d")
                {
                    $table=$linkdir_merge;
                    $column=get_linkdir_merge_column($policy);
                }
                else
                {
                    $table=$conflict_merge;
                    $column=get_conflict_merge_column($policy);
                }
            }
            elsif ($type =~ /f/)
            {
                if (defined $new->{$dentry} and $new->{$dentry} =~ /^(?:d|l->d)$/)
                {
                    $table=$conflict_merge;
                    $column=get_conflict_merge_column($policy);
                }
                elsif (defined $new->{$dentry} and $new->{$dentry} eq "l->f")
                {
                    $table=$linkfile_merge;
                    $column=get_linkfile_merge_column($policy);
                }
                else
                {
                    $table=$file_merge;
                    $column=get_file_merge_column($policy);
                }
            }
            else
            {
                if (defined $new->{$dentry} and $new->{$dentry} eq "d")
                {
                    $table=$directory_merge;
                    $column=get_directory_merge_column($policy);
                }
                elsif (defined $new->{$dentry} and $new->{$dentry} eq "l->d")
                {
                    $table=$linkdir_merge;
                    $column=get_linkdir_merge_column($policy);
                }
                elsif (defined $new->{$dentry} and $new->{$dentry} eq "l->f")
                {
                    $table=$linkfile_merge;
                    $column=get_linkfile_merge_column($policy);
                }
                else
                {
                    $table=$file_merge;
                    $column=get_file_merge_column($policy);
                }
            }

            # Do the merge
            cxlog("$dentry\t($line) $policy\n");
            my $rc=&{$table->{$line}->{$column}}($self, $dentry);
            if ($rc ne "ignore")
            {
                $stub->{$dentry}=$type;
                push @dirs, "$dentry/" if ($type eq "d" and $rc ne "skip");
            }
        }
        closedir($dh);
    }


    # Create new files
    cxlog("\nMerging new files from '$refdir':\n");
    my $skip;
    foreach my $entry (sort keys %$new)
    {
        # Skip sub-directory trees if told to
        next if (defined $skip and $entry =~ /^\Q$skip\E/);
        $skip=undef;
        next if (defined $re_skip and $entry =~ /$re_skip/);

        # Skip existing files and directories
        if (defined $stub->{$entry})
        {
            if ($new->{$entry} eq "d" and $stub->{$entry} eq "l->md")
            {
                # If the destination entry is a symbolic link to the
                # corresponding reference directory, then skip the
                # sub-directories since they are going to match anyway.
                $skip="$entry/";
            }
            next;
        }

        # Determine which policy applies to this entry
        my $policy=$self->merge_tree_get_policy($entry);

        # Handle the 'delete' and 'ignore' policies
        if ($policy =~ /(?:delete|ignore)/)
        {
            # Skip sub-directories if any
            $skip="$entry/";
            next;
        }

        # If doing a full copy, then tweak the policy here
        # so that the user ends up with a standalone copy
        $policy="copy,replacedirs,replacefiles" if ($self->{full_copy});

        # Determine which merge action to take
        my ($table, $line, $column);
        $line=(exists $old->{$entry} ? "e" : "-") . ";-;$new->{$entry}";
        if ($new->{$entry} eq "d")
        {
            $table=$directory_merge;
            $column=get_directory_merge_column($policy);

        }
        elsif ($new->{$entry} eq "l->d")
        {
            $table=$linkdir_merge;
            $column=get_linkdir_merge_column($policy);
        }
        elsif ($new->{$entry} eq "l->f")
        {
            $table=$linkfile_merge;
            $column=get_linkfile_merge_column($policy);
        }
        else
        {
            $table=$file_merge;
            $column=get_file_merge_column($policy);
        }

        # Do the merge
        cxlog("$entry\t($line) $policy\n");
        my $rc=&{$table->{$line}->{$column}}($self, $entry);
        $skip="$entry/" if ($rc ne "recurse");
    }
}


#
# Functions to dump the task list (for debugging)
#

sub dump_list($$)
{
    my ($msg, $list)=@_;

    if ($list and @$list)
    {
        cxlog("$msg:\n");
        foreach my $entry (sort @$list)
        {
            cxlog("   $entry\n");
        }
        cxlog("\n");
        return 1;
    }
    return 0;
}

sub dump_array_list($$)
{
    my ($msg, $list)=@_;

    if ($list and @$list)
    {
        cxlog("$msg:\n");
        foreach my $entry (sort @$list)
        {
            my ($src, $dst)=@$entry;
            cxlog("   $src -> $dst\n");
        }
        cxlog("\n");
        return 1;
    }
    return 0;
}

sub dump_task_list($)
{
    my ($self)=@_;
    if (CXLog::is_on())
    {
        cxlog("Task list:\n");
        my $rc=0;
        $rc|=dump_list("mkdir", $self->{to_mkdir});
        $rc|=dump_list("rmdir", $self->{to_rmdir});
        $rc|=dump_list("rm -rf", $self->{to_rmtree});
        $rc|=dump_list("rm", $self->{to_rm});
        $rc|=dump_list("ln", $self->{to_ln});
        $rc|=dump_list("ln -s", $self->{to_lns});
        $rc|=dump_array_list("cpln", $self->{to_cpln});
        $rc|=dump_list("cp", $self->{to_cp});
        $rc|=dump_list("regpreserve", $self->{to_regpreserve});
        $rc|=dump_list("regoverride", $self->{to_regoverride});
        cxlog("Nothing to do\n") if (!$rc);
    }
}


#
# Functions to do the merge
#

sub do_link_symbolic($$)
{
    my ($src, $dst)=@_;
    return symlink($src, $dst);
}

sub do_link_hard($$)
{
    my ($src, $dst)=@_;
    return link($src, $dst);
}

sub do_link_hard_symbolic($$)
{
    my ($src, $dst)=@_;
    return 1 if (link($src, $dst));
    return 1 if (symlink($src, $dst));
    return 0;
}

sub do_regappend($$)
{
    my ($src, $dst)=@_;
    my $in;
    return 0 if (!open($in, "<", $src));
    my $out;
    if (!open($out, ">>", $dst))
    {
        close($in);
        return 0;
    }
    # Skip the first line to avoid warnings when Wine starts
    my $skip=1;
    my $oldline;
    while (my $line=<$in>)
    {
        if ($line =~ /^\[/)
        {
            $oldline=$line;
            $skip=undef;
        }
        elsif ($line =~ /^#link/)
        {
            # Duplicated registry symbolic links confuse wineserver. Ideally
            # we'd check if they are already present in the first half of the
            # file but since we don't know what's there we just skip them.
            $skip=1;
        }
        elsif (!$skip)
        {
            if (defined $oldline)
            {
                print $out $oldline;
                $oldline=undef;
            }
            print $out $line;
        }
    }
    close($in);
    close($out);
    return 1;
}

sub apply_changes($)
{
    my ($self)=@_;
    my $refdir=$self->{refdir};
    my $dstdir=$self->{dstdir};

    # Delete old files
    if ($self->{to_rm})
    {
        foreach my $entry (@{$self->{to_rm}})
        {
            if (!unlink "$dstdir/$entry")
            {
                cxwarn("unable to delete '$dstdir/$entry': $!\n");
            }
        }
    }

    # Delete old directories
    if ($self->{to_rmdir})
    {
        foreach my $entry (sort {$b cmp $a} @{$self->{to_rmdir}})
        {
            # We only use rmdir to delete directories that might be empty.
            # So it's ok if it fails.
            rmdir "$dstdir/$entry";
        }
    }
    if ($self->{to_rmtree})
    {
        require File::Path;
        foreach my $entry (sort {$b cmp $a} @{$self->{to_rmtree}})
        {
            if (-d "$dstdir/$entry" and !File::Path::rmtree("$dstdir/$entry"))
            {
                cxwarn("unable to delete the '$dstdir/$entry' directory: $!\n");
            }
        }
    }

    # Create new directories
    if ($self->{to_mkdir})
    {
        foreach my $entry (sort @{$self->{to_mkdir}})
        {
            if (!mkdir("$dstdir/$entry", 0777))
            {
                cxwarn("unable to create the '$dstdir/$entry' directory: $!\n");
            }
        }
    }

    # Create symbolic links (to directories)
    if ($self->{to_lns})
    {
        foreach my $entry (@{$self->{to_lns}})
        {
            my $rc=do_link_symbolic("$refdir/$entry", "$dstdir/$entry");
            if (!$rc)
            {
                cxwarn("unable to link '$dstdir/$entry': $!\n");
            }
        }
    }

    # Create links to files
    if ($self->{to_ln})
    {
        my $dolink=$self->{dolink};
        foreach my $entry (@{$self->{to_ln}})
        {
            my $rc=&$dolink("$refdir/$entry", "$dstdir/$entry");
            if (!$rc)
            {
                cxwarn("unable to link '$dstdir/$entry': $!\n");
            }
        }
    }

    # Replicate symbolic links
    if ($self->{to_cpln})
    {
        foreach my $entry (@{$self->{to_cpln}})
        {
            my ($src, $dst)=@$entry;
            my $rc=do_link_symbolic($dst, "$dstdir/$src");
            if (!$rc)
            {
                cxwarn("unable to link '$dstdir/$src' to '$dst': $!\n");
            }
        }
    }

    # Copy new files
    if ($self->{to_cp})
    {
        my $now=time;
        require File::Copy;
        # Setuid and setgid don't make sense for bottle files and
        # are dangerous. So remove them. Allow the sticky bit though.
        # Also apply the user's umask since these will be his files.
        my $modmask=01777 & ~umask();
        foreach my $entry (@{$self->{to_cp}})
        {
            if (File::Copy::copy("$refdir/$entry", "$dstdir/$entry"))
            {
                # Preserve the modification time and the permissions
                utime $now, $self->{new}->{timestamps}->{$entry}, "$dstdir/$entry";
                my $perms=(stat("$refdir/$entry"))[2];
                
                $perms = $perms | 0600 if ($perms & 0400);
                $perms = $perms | 0660 if ($perms & 0440);

                chmod($perms & $modmask, "$dstdir/$entry");
            }
            else
            {
                cxwarn("unable to copy '$dstdir/$entry': $!\n");
            }
        }
    }

    # Merge the registry, preserving the user modifications
    if ($self->{to_regpreserve})
    {
        require File::Copy;
        foreach my $entry (@{$self->{to_regpreserve}})
        {
            if (!rename("$dstdir/$entry", "$dstdir/$entry.$$"))
            {
                cxwarn("unable to merge '$dstdir/$entry' (preserve/rename): $!\n");
                next;
            }
            if (!File::Copy::copy("$refdir/$entry", "$dstdir/$entry"))
            {
                cxwarn("unable to merge '$dstdir/$entry' (preserve/copy): $!\n");
                rename("$dstdir/$entry.$$", "$dstdir/$entry");
                next;
            }
            if (!do_regappend("$dstdir/$entry.$$", "$dstdir/$entry"))
            {
                cxwarn("unable to merge '$dstdir/$entry' (preserve/regappend)\n");
                rename("$dstdir/$entry.$$", "$dstdir/$entry");
            }
            else
            {
                unlink("$dstdir/$entry.$$");
            }
        }
    }

    # Merge the registry, overriding the user modifications
    if ($self->{to_regoverride})
    {
        require File::Copy;
        foreach my $entry (@{$self->{to_regoverride}})
        {
            if (!File::Copy::copy("$dstdir/$entry", "$dstdir/$entry.$$"))
            {
                cxwarn("unable to merge '$dstdir/$entry' (override/copy): $!\n");
                next;
            }
            if (!do_regappend("$refdir/$entry", "$dstdir/$entry"))
            {
                cxwarn("unable to merge '$dstdir/$entry' (override/regappend)\n");
                rename("$dstdir/$entry.$$", "$dstdir/$entry");
            }
            else
            {
                unlink("$dstdir/$entry.$$");
            }
        }
    }
}

return 1;