package Mkcd::Disc;

my $VERSION = '0.1.1';

use strict;
use File::Path;
use Mkcd::Functions;
use Mkcd::Tools qw(du compute_md5 log_ include_md5);
use Mkcd::Package qw(list_hdlist);

=head1 NAME

Disc - mkcd disc functions

=head1 SYNOPSYS

    require Mkcd::Disc;

=head1 DESCRIPTION

C<Mkcd::Disc> include the mkcd disc handling subroutines.

=head1 SEE ALSO

mkcd

=head1 COPYRIGHT

Copyright (C) 2000-2004 Mandrakesoft <warly@mandrakesoft.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

=cut

my $config;

sub new {
    my ($class, $conf) = @_;
    $config = $conf;
    bless {
	   config       => $conf,
	   functions	=> new Mkcd::Functions($config)
	  }, $class;
}

# FIXME must add space for synthesis, however they are negligeable compared to hdlist. Only
# a pb with very small CD.

sub guessHdlistSize {
    my ($class, $group, $size, $cdsize, $lists, $discsFiles) = @_;
    my $FACTOR = 130;
    my $SynFACTOR = 90;
    my $msg;
    my $depsRep = "$config->{tmp}/$class->{config}{name}/$group->{depsrep}";
    $msg = "guessHdlistSize: depsRep $depsRep\n";
    # FIXME heuristic for hdlist size on installation disc, (RPMS size / $FACTOR) per discs
    # need genDeps to write hdlist/synthesis, overkill
    my $depsSize = du($depsRep);
    my $instdisc = $group->{installDisc};
    my $sz;
    my (@notdone, @rem_size);
    push @rem_size, @$cdsize;
    foreach my $list (keys %{$group->{list}}) {
	$msg .= "guessHdlistSize: list $list\n";
	if ($config->{list}[$list]{auto}) {
	    $msg .= "guessHdlistSize: auto mode\n";
	    if ($config->{list}[$list]{cd}) {
		my $tsize = ($config->{discsize}/$FACTOR) * $config->{list}[$list]{cd}; 
		$sz += $tsize < $depsSize ? $tsize : $depsSize;
	    } else {
	       	$sz += $depsSize
	    }
	} else {
	    $msg .= "guessHdlistSize: normal mode\n";
	    my $ok;
	    my $listsize = $group->{listsize}{$list}{rpm};
	    $msg .= "guessHdlistSize: listsize $listsize\n";
	    foreach my $rd (@{$group->{list}{$list}{rpm}}) {
		my ($cdrep, undef, undef, $opt) = @$rd;
		$msg .= "guessHdlistSize: cd $cdrep\n";
		if ($opt->{nodeps}) { $ok = 1; next }
		if ($lists->{$cdrep}) {
		    if ($listsize > $cdsize->[$cdrep]) {
			$sz += $rem_size[$cdrep] / $FACTOR;
			$listsize -= $rem_size[$cdrep];
			$rem_size[$cdrep] = 0
		    } else {
			$sz += $listsize / $FACTOR;
			$rem_size[$cdrep] -= $listsize;
			last
		    }
		}
	    }
	    $ok and push @notdone, $list	
	}
    }
    $msg .= "guessHdlistSize: reserving ";
    if ($depsSize < $sz && $depsSize > 10000) { 
	$msg .= $depsSize;
	$size->{disc}[$instdisc] += int $depsSize;
	if ($config->{disc}[$instdisc]{function}{data}{installation}[1]{synthesis}) { $size->{disc}[$instdisc] += int($depsSize / $SynFACTOR) }
    } elsif ($sz > 10000) { 
	$msg .= $sz; 
	$size->{disc}[$instdisc] += int $sz;
	if ($config->{disc}[$instdisc]{function}{data}{installation}[1]{synthesis}) { $size->{disc}[$instdisc] += int($sz / $SynFACTOR) }
    } else {
	log_("ERROR guessHdlistSize: possibly wrong estimated dependencies file size\n", $config->{verbose}, $config->{LOG},2) 
    }
    $msg .= " (new size $size->{disc}[$instdisc]) on disc $instdisc ($depsSize/$sz) for dependencies files\n";
    log_($msg, $config->{verbose}, $config->{LOG},2);
    @notdone or return 1;
    $sz = 0;
    foreach my $list (@notdone) {
	foreach my $rd (@{$group->{list}{$list}{rpm}}) {
	    my ($cd, $rep, $repopt, $opt) = @$rd;
	    if ($lists->{$cd} == 1) {
		$sz += du("$class-{config}{topdir}/build/$class->{config}{name}/$cd/$class->{config}{disc}[$cd]{function}{data}{dir}{$rep}[1]{reploc}")
	    } elsif ($lists->{$cd} == 2) {
		foreach my $rpm (keys %{$discsFiles->[$cd]{$rep}{$list}}) {
		    $sz += du("$discsFiles->[$cd]{$rep}{$list}{$rpm}/$rpm.rpm")
		}
	    }
	}
    }
    $sz /= $FACTOR;
    $msg = "guessHdlistSize: reserving $sz";
    $size->{disc}[$instdisc] += $sz;
    if ($config->{disc}[$instdisc]{function}{data}{installation}[1]{synthesis}) { $size->{disc}[$instdisc] += $sz / $SynFACTOR }
    $msg .= " (new size $size->{disc}[$instdisc]) on disc $instdisc ($sz) for extra dependencies files\n";
    log_($msg, $config->{verbose}, $config->{LOG},1)
}		

sub getBuiltDiscs {
    my ($class, $lists, $group, $discsFiles) = @_;
    foreach my $l (keys %{$group->{list}}) {
	log_("getBuiltDiscs: get rep from list $l\n", $config->{verbose}, $config->{LOG},2);
	my @rpmlist;
	ref $group->{list}{$l}{rpm} and push @rpmlist, @{$group->{list}{$l}{rpm}};
	ref $group->{list}{$l}{srpm} and push @rpmlist, @{$group->{list}{$l}{srpm}};
	for (my $i; $i < @rpmlist; $i++) {
	    my ($cd, $rep, $repopt, $opt) = @{$rpmlist[$i]};
	    $lists->{$cd} == 1 or next;
	    $class->{config}{list}[$l]{disc}{$cd}{$rep}{done} = 1;
	    if ($opt->{hdlist}) {
		log_("getBuiltDiscs: getting rpm from hdlist $opt->{hdlist}\n", $config->{verbose}, $config->{LOG},2);
		my $tmphdlist = "$class->{config}{tmp}/.mkcd_build_hdlist";
		foreach (@{list_hdlist([$opt->{hdlist}], $config->{verbose}, 1, $tmphdlist)}) {
		    log_("getBuiltDiscs: adding $_\n", $config->{verbose}, $config->{LOG},6);
		    $discsFiles->[$cd]{$rep}{$l}{$_} = ''
		}
	    } else { 
		my $dir = "$class->{config}{topdir}/build/$class->{config}{name}/$cd/$class->{config}{disc}[$cd]{function}{data}{dir}{$rep}[1]{reploc}";
		#
		# FIXME maybe need to unshift instead of push
		#
		$repopt->{source} ? push(@{$class->{config}{list}[$l]{packages}[0]{srpm}}, $dir) : push(@{$class->{config}{list}[$l]{packages}[0]{rpm}}, $dir);
		log_("getBuiltDiscs: get files from $dir\n", $config->{verbose}, $config->{LOG},2);
		opendir my $A, $dir;
		foreach (readdir $A) {
		    /(.*)\.rpm/ or next;
		    # FIXME need to check if it is well placed in getList function
		    # $group->{done}{$rpm} = $group->{orderedrep}{"$cd/$rep"};
		    # log_("getBuiltDiscs: adding $1 in $dir for $cd/$rep list $l\n", $config->{verbose}, $config->{LOG},2);
		    $discsFiles->[$cd]{$rep}{$l}{$1} = $dir
		}
	    }
	}
    }
    1
}

sub write_graft {
    my ($graft, $file, $exclude) = @_;
    log_("write_graft: $file ($graft)\n", $config->{verbose}, $config->{LOG},2); 
    open my $A, ">$file";
    open my $B, ">$exclude";
    foreach my $d (sort keys %$graft) {
	if (ref $graft->{$d}) {
	    map { print $A "$d=$_\n" } keys %{$graft->{$d}}
	} elsif ($graft->{$d} == 3) {
	    print $B "$d\n"
	}
    }
}

sub graft_to_md5 {
    my ($graft, $dir, $serial) = @_;
    my $mdfile = ".$serial.md5";
    log_("graft_to_md5: $serial -> $dir/$mdfile ($graft)\n", $config->{verbose}, $config->{LOG},2); 
    local *A; open A, ">$dir/$mdfile";
    my %ignore;
    my @to_check;
    $graft->{$mdfile}{"$dir/$mdfile"} = 0;
    foreach my $f (keys %$graft) { 
	if (ref $graft->{$f}) { 
	    foreach (keys %{$graft->{$f}}) {
		my ($file) = m,/([^/]+)$,;
		my $dest = $f =~ m,/$, ? "/$f/$file" : "/$f";
		if ($graft->{$f}{$_}) { 
		    push @to_check, [ $dest, $_ ];
		} else { 
		    $ignore{$dest} = 1;
		    print A "$f\n" 
		}
	    }
	} else { 
	    $ignore{$f} = 1;
	    print A "$f\n" 
	}
    }
    my $digest = compute_md5(\@to_check, \%ignore);
    print A "$digest - $serial\n"
}   

sub makeDiscs {
    my ($class, $fixed, $lists, $cds, $size, $mkisos, $discsFile, $graft, $sort, $inode, $cdfile) = @_;
    my $dir;
    my $name = $class->{config}{name};
    my $topdir = $class->{config}{topdir};
    my $tmp = "$config->{tmp}/build/$name";
    my $first;
    my $isodir = $class->{config}{isodir} ? $class->{config}{isodir} : "$topdir/iso/$name";
    
    if (!$class->{config}{nolive}) {
	$dir = "$topdir/build/$name";
	-d $dir or mkpath $dir;
	-d $tmp or mkpath $tmp;
    } else {
	$dir = "$config->{tmp}/build/$name";
	-d $dir or mkpath $dir;
    }
    if ($fixed == -1) {
	buildISO($class->{config}, $isodir, $name, $lists, $fixed, $mkisos, $size, $cds, $cdfile, $sort, $first, 0);
	return 1
    }
    log_("makeDiscs: Discs @$cds topdir $dir\n", $config->{verbose}, $config->{LOG},1);
    foreach my $i (@$cds) {
	$lists->{$i} > 1 or next;
	my $cd = $class->{config}{disc}[$i];
	$graft->{$i} ||= {};
	$sort ||= {};
	if ($fixed > 1 && $cdfile->[$i] == 0) { 
	    log_("makeDiscs: nothing to do for disc $i\n", $config->{verbose}, $config->{LOG},2);
	    next 
	}
	if (!$fixed) {
	    log_("makeDisc: Fixed part of disc $i\n", $config->{verbose}, $config->{LOG},3);
	    if ($class->{config}{nolive}) {
		log_("makeDisc: removing $dir/$i.list\n", $config->{verbose}, $config->{LOG},3);
		-f "$dir/$i.list" and unlink "$dir/$i.list";
		log_("makeDisc: removing $dir/$i\n", $config->{verbose}, $config->{LOG},3);
		rmtree "$dir/$i";
		mkdir "$dir/$i"
	    } else {
	        -d "$tmp/$i" or mkpath "$tmp/$i";
		foreach ("$topdir/build/$name/$i", "$topdir/build/$name/first/$i") { rmtree $_; mkdir $_ } 
		$first = "$topdir/build/$name/first/$i"
	    }
	} else { log_("Finalizing disc $i\n", $config->{verbose}, $config->{LOG},2) }
	my $sz;
	if (ref $cd->{steps}) {
	    for (my $j; $j < @{$cd->{steps}}; $j++) {
		my $name = $cd->{steps}[$j][0];
		log_("makeDiscs: $name ($fixed)\n", $config->{verbose}, $config->{LOG},2);
		if (defined $Mkcd::Functions::{$name}) { 
		    $sz += &{$Mkcd::Functions::{$name}}($class->{disc}{functions}, $cd->{steps}[$j], $dir, $fixed, $class->{config}{nolive}, $i, $cd, $cdfile, $lists, $mkisos, $graft, $inode->{$i}, $discsFile, $sort) 
		}
		else { log_("ERROR: unrecognized function name $name\n",0, $config->{LOG}) }
		log_("SIZE ($name) $sz\n", $config->{verbose}, $config->{LOG},4);
	    }
	} else {
	    die "FATAL make_discs: impossible to find definition of disc $i, problem in config file ?"
	}
	if ($class->{config}{nolive}) {
	    log_("SIZE $size->{disc}[$i] + $sz\n", $config->{verbose}, $config->{LOG},4);
	    $size->{disc}[$i] += $sz
	} else {
	    $size->{disc}[$i] = du("$dir/$i") + $sz
	}
	log_("disc $i ($dir/$i) size: $size->{disc}[$i] ($sz)\n", $config->{verbose}, $config->{LOG},3);
	my $mkisoopt = $class->{config}{mkisoopt};
	if ($fixed) {
	    $graft->{$i}{".rr_moved"} = 0;
	    my $publisher = $config->{Publisher} || $config->{disc}[$i]{Publisher};
	    my $commkiso = qq(-A "$cd->{appname}" -publisher "$publisher" -volset "$cd->{serial}" -V "$cd->{label}" -o $isodir/$i-$name.iso);
	    if ($config->{nolive}) {
		# include_md5 replaces md5 per files
		#graft_to_md5($graft->{$i},"$dir/$i",$cd->{serial});
		write_graft($graft->{$i}, "$dir/$i.list", "$dir/$i-exclude.list");
		#$mkisos->[$i] = "$mkisoopt -graft-points -path-list $dir/$i.list -sort $dir/$i.sort " . (-f "$dir/$i-exclude.list" ? "-exclude-list $dir/$i-exclude.list" : "") . " $commkiso $mkisos->[$i]" if $fixed == 1
		$mkisos->[$i] = "$mkisoopt -graft-points -path-list $dir/$i.list " . (-f "$dir/$i-exclude.list" ? "-exclude-list $dir/$i-exclude.list" : "") . " $commkiso $mkisos->[$i]" if $fixed == 1
	    } else {
		$graft->{$i}{"/"}{"$dir/$i/"} = 1;
		# include_md5 replaces md5 per files
		#graft_to_md5($graft->{$i},"$dir/$i",$cd->{serial});
		if ($mkisos->[$i]) { 
		    $mkisos->[$i] = "$mkisoopt $commkiso $mkisos->[$i] $dir/$i" if $fixed == 1
		} else { 
		    $mkisos->[$i] = qq($mkisoopt $commkiso "$dir/$i") if $fixed == 1
		}
	    }
	}
    }
    !$fixed and return 1;
    buildISO($class->{config}, $isodir, $name, $lists, $fixed, $mkisos, $size, $cds, $cdfile, $sort, $first, 1);
    1
}

sub buildISO {
    my ($config, $isodir, $name, $lists, $fixed, $mkisos, $size, $cds, $cdfile, $sort, $first, $checksize) = @_;
    my $log = $config->{LOG};
    $isodir or return;
    -d $isodir or mkpath $isodir; 
    log_("buildISO: isodir $isodir\n", $config->{verbose}, $config->{LOG}, 5);
    my $dir = "$config->{tmp}/build/$name";
    compute_sort_file($sort, $cds, $dir, $first) if !$checksize;
    foreach my $i (@$cds) {
	$lists->{$i} > 1 or next;
	if ($fixed > 1 && $cdfile->[$i] == 0) { 
	    log_("buildISO: nothing to do for disc $i\n",0, $config->{LOG});
	    next 
	}
	my $sort_cmd = "-sort $dir/$i.sort " if ref $sort->{$i};
	my $cmd = $checksize ? "mkisofs -print-size -quiet $mkisos->[$i]" : "mkisofs $sort_cmd$mkisos->[$i]";
	if ($checksize) {
	    $size->{disc}[$i] = 1024 * 2 * `$cmd`;
	    log_("MKISOFS disc $i size $size->{disc}[$i]\n", $config->{verbose}, $config->{LOG},1);
	} elsif (!$config->{noiso}) { 
	    $cmd .= " > /dev/null" if !$config->{verbose};
	    my $err = system $cmd;
	    log_("disc $i: $cmd\n", 1, $config->{LOG});
	    if ($err) { 
		log_("ERROR: disc $i $cmd failed ($!)\n", 1, $config->{LOG});
		print $log "WARNING: a problem may have appear, if ISOs files are not OK and you want to retry to build the ISOs, type the following command:
		$cmd\n "
	    }
	    my $boot_cat_location = `isoinfo -l -R -i $isodir/$i-$name.iso`;
	    $boot_cat_location =~ /.*\[\s*(\d+) \d\d]  boot.cat.*/m;
	    log_("buildISO: checking boot.cat location ($1)\n", 5, $config->{LOG});
	    die "FATAL buildISO: boot.cat at $1" if $1 == 929 || $1 == 740;
	    my $ok = include_md5("$isodir/$i-$name.iso",1);
	    log_("ERROR: disc $i include_md5 failed ($err)\n", 1, $config->{LOG}) if !$ok;
	    $size->{disc}[$i] = du("$isodir/$i-$name.iso")
	}
    }
}

sub checkSize {
    my ($class, $n, $size, $cdsize, $cds, $rejected) = @_;
    my $ok = 1;
    foreach my $i (@$cds) {
	if ($size->{save}{disc}[$i] != $size->{disc}[$i]) {
	    $size->{save}{disc}[$i] = $size->{disc}[$i];
	    $ok = 0
	}
    }
    if ($ok) {
	log_("checkSize: disc sizes has not changed, exiting\n",1, $config->{LOG});
	return 1
    }
    my $ok = 1;
    foreach my $i (@$cds) {
	$size->{disc}[$i] or next;
	my $origcdsize = $class->{config}{disc}[$i]{size};
	log_("checkSize: disc $i size $size->{disc}[$i] ($origcdsize)\n",1, $config->{LOG});
	my $d = $size->{disc}[$i] - $origcdsize;
	if ($size->{disc}[$i] > $origcdsize) {
	    if ($d > $origcdsize/10) {
		log_("ERROR: an error must have happen, disc $i is far too big ($size->{disc}[$i] > $origcdsize), ignoring\n",1, $config->{LOG});
		next
	    }
	    if ($d > 0 && $d > ($origcdsize*$n)/1000) { 
		$ok = 0;
		$cdsize->[$i] -= $d;
		log_("ERROR: disc $i is too big ($size->{disc}[$i] > $origcdsize ($d)\n",1, $config->{LOG})
	    } else {
		$cdsize->[$i] = $size->{disc}[$i]+1;	   
	    }
	} else {
	    if ($d < 0 && $rejected) {
		$d = -$d;
		# FIXME heuristic: do not change CD size if diff is greater than 10% of the original CD size
		if ($d > $origcdsize/10) {
		    log_("ERROR: an error must have happen, disc $i is far too small ($size->{disc}[$i] << $origcdsize), ignoring\n",1, $config->{LOG});
		    next
		}
		if ($d > ($origcdsize*$n)/300) { 
		    $ok = 0;
		    #$cdsize->[$i] += $d/2;
		    log_("ERROR: disc $i is too small ($size->{disc}[$i] < $origcdsize, ($d)\n",1, $config->{LOG}) 
		}
	    }
	}
	log_("checkSize: new disc $i size $cdsize->[$i]\n",1, $config->{LOG});
    }
    return $ok
}

sub compute_sort_file {
    my ($sort, $cds, $dir, $first) = @_;
    foreach my $cd (@$cds) {
	ref $sort->{$cd} or next;
	open my $F, ">$dir/$cd.sort" or return 0;
	my $i=1;
	my %done;
	foreach my $chunks (@{$sort->{$cd}}) {
	    foreach my $f (@$chunks) {
		$f or next;
		$done{$f} and next;
		$done{$f} = 1;
		print $F "$f $i\n";
		$i++
	    }
	}
	print $F "$first $i\n" if $first
    }
}

1

# Changelog
#
# 2002 05 22
# fix a pb in graft_to_md5 that made dest incomplete when dest is a directory
#
# 2002 08 25
# improve checkSize to better work with optimize_space
