#!/usr/bin/perl

#
# By: John Stile <johns at stilen.com>
# Created: Wed May 13 19:29:18 PST 2009
# Purpose: Burn all repos to DVD
#          Must do funky stuff minimize DVD's needed
# Version  0.20
#
use strict;
use File::Find;
use File::Path;

my $debug=1;
my $backup_home="/home/svn_backup";  # directory where backups exist
my %Repo;                            # hash holds repo name as key, size as value
my $disk_size=4400000000;            # bytes on a dvd
my $date=`date +%Y%m%d`;             # date in format YYYYMMDD
my $mount_dir="/mnt";                # Location where we will mount and burn from
chomp($date);                        # Remove newline from date

#
# Print debug mode warning message
#
if ( $debug )
{
    print "***** DEBUG MODE *****\n";
}

# 
# Cleanup junk from previous run
#
my @old_disks=</mnt/Disk*>;
for my $i (@old_disks){ 
  print "Clearning $i\n";
  opendir DIR, $i or die "cannot open dir $i: $!";
  my @things_to_unmount=readdir DIR;
  for my $j (@things_to_unmount){
       print "unmounting $i/$j\n";
      `umount ${i}/${j}`;
  }
  print "Removing $i\n";
  rmtree(["$i"]);
}

#
# Get a list of repos in backup directory
# Exclude ., .., *.bak, *.txt, and *.iso
#
opendir DIR, $backup_home or die "cannot open dir $backup_home: $!";
my @directories = grep { $_ ne '.' && $_ ne '..' && $_ !~ /\.bak$/ && $_ !~ /\.txt$/ && $_ !~ /\.iso$/ && $_ !~ /^Disk/ } readdir DIR;
closedir DIR;

#
# Generate hash with 
#  - directory name as key,
#  - directory size as value
#
foreach my $directory (@directories) { 
  my $size=&dir_size($backup_home."/".$directory);
  $Repo{$directory}=$size;
  if ($debug){ print "$directory\t$Repo{$directory}\n";}
}

#
# Called in above stanza.
# Takes absolute path to repo directory.
# Returns size of that repo
#
sub dir_size {
    my $dir = shift;
    die "Directory expected as parameter" if !-d $dir;
    my $size_total = 0; 
    find( 
      {
        follow => 0, 
        wanted => sub {
	$size_total += -s $File::Find::name || 0;
       }
    }, $dir); 
    return $size_total;
} 

#
# 4.7GB fits on a DVD
#
# While there are repos to write
#  While there is space on a dvd
#    If current will fit on dvd, 
#       add to dvd list and remove from hash
#    If current will not fit on dvd, 
#       add to left_over array
#  Once all space is gone, 
# 
##  Array holding all repos to backup
my @available_repos=keys(%Repo);

## Hash of arrays, holds repos to burn
my %DVD;
my $counter=1;

## size of disk
my $current_size=0;

# fill the disk
&fill_disk( @available_repos );


sub fill_disk {
   # store the array passed in
   my @rep =  @_;

   # empty array to hold what was left over
   my @leftover;

   ## loop until we run out of available repos @available_repos
   foreach my $item ( @rep ){

     ## If there is space on the DVD
     if ( $current_size <= $disk_size ){

       ## If the repo will fit on the DVD
       if ( $Repo{$item} <= ($disk_size - $current_size) ){

   	 ## Add current repo to disk size
   	 $current_size += $Repo{$item};
 
   	 ## Add they key to array for current DVD
   	 push ( @{$DVD{$counter}},$item);
 
   	 ## Print what we will add the repo
   	 if ($debug){ print "To DVD$counter Add Repo:$item of Size:($Repo{$item}) \tTotal:$current_size\n";}
 
   	 # Go to the next iteration of the loop
   	 next;
       }
       ##
       push (@leftover, $item);
     } else {
       print "Won't Fit, Increment counter and zero current_size\n";
       $counter++;
       $current_size=0;
     }
   }
  if ($debug){ print "Array processed\n";}
  $counter++;
  $current_size=0;
  if ( @leftover ){
    &fill_disk(@leftover);
  }
}

#
# Make ISO  and Burn
#
# Change to backup directory
chdir $backup_home || die "Can't change directory\n";
# Process each disk, one at a time
foreach my $akey (keys %DVD){
  print "Making Disk${akey}\n";  
  # mkisofs will only take a single directory as an argument
  # Here we create the directory mkisofs will use,
  # Then 'mount -o bind' all the repositoreis into that directory
  mkpath(["$mount_dir"."/"."Disk$akey"])  || die "Can't make directory Disk${akey}\n";
  chdir "$mount_dir"."/"."Disk$akey" ;
    foreach my $bkey (@{$DVD{$akey}}){
       mkpath("$bkey",{verbose => 1}) || die "Can't make directory $bkey\n";
       `mount -o bind "${backup_home}/${bkey}" ${bkey}`;
    }
  chdir $mount_dir || die "Can't change directory\n";
  print "Current Working Directory: ".`pwd`."\n";
  print "Burning Directory: Disk${akey}/\n";
  # Eject and ask for a disk.
  `eject`;
  print "Please feed me a blank DVD, and press Enter\n";
  my $foo=<STDIN>;
  # mkisofs and burn 
  print "growisofs -dvd-compat -Z /dev/dvdrw -joliet-long -R -V \"REPSITORY_BACKUP_${date}_Disk${akey}\" Disk${akey}/\n";
  `growisofs -dvd-compat -Z /dev/dvdrw -joliet-long -R -V "REPSITORY_BACKUP_${date}_Disk${akey}" Disk${akey}/ `;
  # unmont and remove
  print "Finished. Unmounting and removing Disk$akey\n";
  opendir DIR, "$mount_dir/Disk$akey" or die "cannot open dir $mount_dir/Disk$akey: $!";
  my @things_to_unmount=readdir DIR;
  for my $j (@things_to_unmount){
       print "unmounting $mount_dir/Disk$akey/$j\n";
      `umount $mount_dir/Disk$akey/$j`;
  }  
  # remove the Disk directory
  rmtree(["$mount_dir/Disk$akey"]); # from module File::Path
}
# eject the last disk
`eject`;

