#!/bin/bash

# ALT distribute: distribute pkg collection on multiple CDs with (APT) indices.
# Copyright (C) 2002 Ivan Zakharyaschev <imz@altlinux.ru>.
#
# 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
# of the License, 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.


#set -x

# Changes:
# <lav@altlinux.ru>, 2008 Nov 24:
# - use subst instead php for html preparing
# <lav@altlinux.ru>, 2005 Jan 09:
# - Add --recordcd option for record CD disk immediately, without ISO image
# - REMOVED: add filename encoding variable
# <lav@altlinux.ru>, 2004 Sep 10:
# - Fix getSize function for new coreutils
# - Add --recorddvd option for record DVD disk immediately, without ISO image
# - Add pause between multiple disk recording
#
# <imz@altlinux.ru>, 2002 Oct 21:
# - Fix stopping after clearing is PASSED (nothing to clear):
#   + make all echo_*() always return successfully;
#   + make passed() return successfully.
# - 0.4.0
#
# <imz@altlinux.ru>, 2002 Oct  7:
# - Rewrite the layouting code (splitMirrorDir()):
#   + fix too long args list when creating links
#   + fix not counting the first package's size in every disk (that's why they used to be a little larger tahn requested)
#   + speed up by means of less calls to getSize() utility (also new)
#     and no calls to `basename' (use shell param expansion which I consider 
#     to be '\n'-safer and faster)
#   + use a format-argument for stat (in getSize())
# - more strict error code monitoring (should fail on any errors)
# - add messages to the clearing stage
# - FIXME: now using cp instead of ln in eatFiles() (due to a present bug in ln)
# - 0.3.93
#
# <imz@altlinux.ru>, 2002 Jul 11:
# - fix TERM exporting in functions for tput;
# - remove -speed option when we call cdrecord(1)
#   (please use env vars instead!);
# - 0.3.91.
#
# <imz@altlinux.ru>, 2002 Jul 11:
# - fix snap-shooting (fix-base) when the source mirror directory is a symlink;
# - use ncurses tput(1) program to format messages;
# - pass PREFIX and main ARCH to disk-description.php4 (and make use of them);
# - add an optional comment for supplementary archs in disk-description.php4;
# - 0.3.9.
#
# <imz@altlinux.ru>, 2002 Jul 10: 
#  - 0.3.8.
#
# Look at the output of `distribute --usage' for inforamtion on how to use it.
# Some other documentaion is also supplied with the program 
# (look for it in the appropriate directory under /usr/share/doc/).

trap failure ERR
set -o errexit

TASKNAME="$1"

# A rather stupid if-statement. (I think so because unset and zero-length 
# variables are handled almost in the same way by Bash.) It is here 
# just to remember how important P_ROOT is.
if [[ ! "$P_ROOT" ]]; then
  P_ROOT=""
fi

PATHS="$P_ROOT/usr/share/distribute/system.conf"

function MSourceIfNotEmpty()
{
  for f in "$@"; do 
      if [ -s "$f" ]; then 
	  . "$f"
      fi
  done
}

function MkAndPushDir()
{
  for d in "$@"; do 
    mkdir -p "$d" && pushd "$d" &>/dev/null || return 1
  done
}
            
##############################
# Reading parameters.

MSourceIfNotEmpty "$PATHS"

# And these paths should be defined in the ditribution specific 
# configuration file (have just read it)

MSourceIfNotEmpty "$CONF_DEFAULT"
MSourceIfNotEmpty "$CONF_FROM_ADMIN"
MSourceIfNotEmpty "$CONF_FROM_USER"

BASES_DIR="$D_WORKPLACE/bases"
LAYOUTS_DIR="$D_WORKPLACE/layouts"
ISOS_DIR="$D_WORKPLACE/ISOs"

THIS_BASES_DIR="$BASES_DIR/$TASKSPEC"
THIS_LAYOUTS_DIR="$LAYOUTS_DIR/$TASKSPEC"
THIS_ISOS_DIR="$ISOS_DIR/$TASKSPEC"

# Task specific confgi will be read later

# End of reading params
############################

MSourceIfNotEmpty "$HELPDATA"
MSourceIfNotEmpty "$FUNCTIONS"

# Simple commands:

# The first arg is the name of the task usually; except for a few cases:
case "$1" in

--gen-myconf)
  if [[ "$2" ]]; then
    if [[ "$2" == "-" ]]; then
      where=/dev/stdout
    else
      where="$2"
    fi
  else
    where="$CONF_FROM_USER"
  fi
  mkdir -p "$(dirname "$where")" \
  && printf $"Writing an example of general user's configuration to %s.\n" "$where" >&2 \
  && general_conf > "$where" || exit 1
  echo $"Edit it to tune your configuration." >&2
  exit 0
  ;;

--usage)
  usage
  exit 0
  ;;

--help)
  help
  exit 0
  ;;

esac

shift

if [[ "$#" == "0" ]]; then
  usage
  exit 1
fi

enter_stage $"Configuration"
start_action $"for task %s" "$TASKNAME"
find_and_read_CONF_FOR_TASK \
&& { printf  $"read from %s" "$CONF_FOR_TASK"; success; } \
|| { printf $"is supposed to be in %s" "$CONF_FOR_TASK"; failure; }

case "$1" in

--gen-taskconf)
  if [[ "$2" ]]; then
    if [[ "$2" == "-" ]]; then
      where=/dev/stdout
    else
      where="$2"
    fi
  else
    where="$CONF_FOR_TASK"
  fi
  mkdir -p "$(dirname "$where")" \
  && printf $"Writing an example of a task configuration to %s.\n" "$where" >&2 \
  && task_conf > "$where" || exit 1
  echo $"Edit it to tune the parameters of the task." >&2
  exit 0
  ;;

esac


#################################
# Checking the params

DOT_SUFFIX=${SUFFIX:+".$SUFFIX"}

if [[ "$INDEX_FOR_APT" == "yes" ]]; then
  if [[ ! "$SUFFIX" ]]; then
    echo $"The suffix is obligatory for CDs indexed for APT! Set it." >&2
    #exit 1
  fi
  if echo "$CD_COLLECTION_TITLE" | fgrep --silent '@'; then
    echo $"Do not use '@' in the title for a CD that stores pkgs for \
APT -- it does not understand this symbol correctly. If it does \
(already), then fix the script" >&2
    exit 1
  fi
fi

if [[ ! "$CDVOLUME" ]]; then
  CDVOLUME=650000000
fi

# End of checking the params.
#######################################


#####################################
# FUNCTIONS

# Very simple functions, perhaps you will alter them:

function zeroDelimited() {
    for p in "$@"; do
	printf '%s\000' "$p"
    done
}

if [[ "$USE_DU_FOR_SIZE" == "yes" ]]; then
    function getSize() { 
	trap failed ERR
	set -o errexit
	zeroDelimited "$@" | xargs -0 --no-run-if-empty \
	    du --dereference-args --dereference --summarize --bytes -- \
	    | cut --fields=1
	#set +o errexit
    }
else
    function getSize() {
#	{ oldIFS="$IFS"; IFS=$'\n'; echo "$FUNCNAME: args: " "$*" | head >&2;  IFS="$oldIFS"; }
	trap failed ERR
	set -o errexit
	zeroDelimited "$@" | xargs -0 --no-run-if-empty \
	    stat --dereference --format='%s ' --
	#set +o errexit
    }
fi
export -f getSize

function mkiso() {
    enter_stage "Making ISO disk images"
    start_action "$1.iso"
  mkdir -p "$THIS_ISOS_DIR" \
    && mkisofs -J -r -f -o "$THIS_ISOS_DIR/$1".iso "$THIS_LAYOUTS_DIR/$1" \
     && success || failure
}

function recordcd() {
    if [ "$2" == "pause" ] ; then
		echo "Press any key to start writing..."
		read
	fi
    enter_stage "Making ISO disk and writing CD disk on fly"
    start_action $"writing %s" "$1"
	mkisofs -J -r -f -quiet "$THIS_LAYOUTS_DIR/$1" | cdrecord -tao -v -eject -\
     && success || failure
}


function recorddvd() {
    if [ "$2" == "pause" ] ; then
		echo "Press any key to start writing..."
		read
	fi
    enter_stage "Writing DVD disk"
    start_action $"writing %s" "$1"
    growisofs -Z /dev/dvd -v -J -r -f "$THIS_LAYOUTS_DIR/$1" \
     && success || failure
}

function record() {
    if [ "$2" == "pause" ] ; then
		echo "Press any key to start writing..."
		read
	fi
    enter_stage "Recording disks"
    start_action $"writing %s" "$1"
    cdrecord -v -eject "$THIS_ISOS_DIR/$1"\
     && success || failure
}

function list() {
    enter_stage $"Listing layouts"
  printf $"The models of the future filesystem of the disks are in %s,\n" "$THIS_LAYOUTS_DIR"
  printf $"listing their sizes (per disk):\n"
  echo $"Human readable form (units are powers of 1024):"
  du --summarize --dereference --human-readable "$THIS_LAYOUTS_DIR"/*
  echo
  echo $"And now in bytes:"
  du --summarize --dereference --bytes "$THIS_LAYOUTS_DIR"/*
}

function clearCDTrees() {
    enter_stage $"Clearing layouts"
    for cd in "$THIS_LAYOUTS_DIR"/cd[0-9]*; do 
	start_action $"removing %s" "$(basename "$cd")"
	if [[ -d "$cd" ]]; then 
	    rm -rf "$cd" && success || failure
	else
	    passed
	fi
    done
}

# A bit more complex:

function apt_index() {
  if [[ "$INDEX_FOR_APT" != "yes" ]]; then
    echo $"Skipping APT indexing stage (as you wanted)." >&2
    return
  fi
  enter_stage $"Indexing"
  for cd in "$THIS_LAYOUTS_DIR"/cd*; do
      for arch in "${ARCH[@]}"; do
	  if [[ ! -d "$cd/$PREFIX/$arch/base" ]]; then
	      start_action $"%s on %s" "$arch" "$cd"
	      passed
	      continue
	  fi
	  if [[ "$arch" == "$ARCH" ]]; then 
	      start_action $"%s(main: bin +all src) on %s" "$arch" "$cd"
	      genbasedir --progress --topdir "$cd" "$PREFIX"/"$arch" "$SUFFIX" \
		  && success || failure
	  else
	      start_action $"%s(other: bin +some src only) on %s" "$arch" "$cd"
	      genbasedir --mapi --progress --topdir "$cd" "$PREFIX"/"$arch" "$SUFFIX" \
		  && success || failure
	  fi 
      done
  done
}

function makePlainIndex() {
  if [[ "$CREATE_PLAIN_INDEX" != "yes" ]]; then
    echo $"Skipping plain indexing." >&2
    return
  fi

  enter_stage $"Making plain indices"

  if [[ ! "$BASE" ]]; then
    echo $"$FUNCNAME: No base name specified!" >&2
    exit 1
  fi

  local ldestdir="$THIS_BASES_DIR/$BASE"
  if [[ ! -d "$ldestdir" ]]; then
    echo $"$FUNCNAME: destination directory ($ldestdir) doesn't exist!" >&2
    exit 1
  fi

  parse_localmirror_table | while read type mirror; do
      start_action $"of the full base-state %s, type %s" "$BASE" "$type"
      local plain_index="$ldestdir/plain-index-$(echo "$type" | sed -e 's!/!_!')"
      (cd "$THIS_BASES_DIR/$BASE/$type"; find . '(' -type l -o -type f ')' -printf '%f\n') \
      | (unset LANG{,UAGE}; sort) | gzip -9 \
      > "$plain_index".gz \
      && zme "$plain_index".gz >/dev/null && success \
      || { rm -f "$plain_index".*; failure; } 
  done
  if [[ "$1" ]]; then
      cp -i "$THIS_BASES_DIR/$BASE/title" "$ldestdir" ||:
  fi
}

# Make a remote copy (publish):
function publishPlainIndex() {
  if [[ "$CREATE_PLAIN_INDEX" != "yes" ]]; then
    echo $"Skipping publishing the plain indexes." >&2
    return
  fi

  if [[ ! "$1" ]]; then
    echo $"Skipping publishing the plain indexes (nowhere to publish)." >&2
      return
  fi

  enter_stage $"Publishing"

  if [[ ! "$BASE" ]]; then
    echo $"$FUNCNAME: No base name specified!" >&2
    exit 1
  fi

  local ldestdir="$THIS_BASES_DIR/$BASE"
  if [[ ! -d "$ldestdir" ]]; then
    echo $"$FUNCNAME: destination directory ($ldestdir) doesn't exist!" >&2
    exit 1
  fi

  start_action $"the plain lists of the full base-state %s" "$BASE"
  local rdestdir="$1"
  "${RSYNC[@]}" -a "$ldestdir"/ \
      --include='plain-index-*' --include='title' --exclude='*' \
      "$rdestdir" \
      && success || failure
}

# Splitting:

function splitMirrorDir() {
  if [[ ! -d "$THIS_BASES_DIR/$BASE/$type" ]]; then
    echo $"No base fixed for $type" >&2
    exit 1
  fi

  # Getting the list of all suitable files:
  local -a allFiles
  let 'no = 0' ||:
  allFiles=()
  # no points to the next free position in allFiles
  # allFiles contains the constructed list
  for p in "$THIS_BASES_DIR/$BASE/$type"/*.rpm; do
      if [[ ! -e "$p" ]]; then
	  # fail on non-existent files
	  echo $"Package file doesn't exist: " "$p" >&2
	  return 1 
      fi
      if [[ "$ONLY_REAL_FILES" == "yes" && ! -f "$p" ]]; then
	  continue
      fi
      if [[ "$DIFF_TO_BASE" ]]; then
          older_copy="$DIFF_TO_BASE/$type/${p##*/}" # using shell param expansion instead of `basename' to speed up
          if [[ -h "$older_copy" || -a "$older_copy" ]]; then
	      continue
	  fi
      fi
      allFiles[$(( no++ ))]="$p"
  done
  readonly -a allFiles

  # Splitting the list of all files into future disks:
  # 
  local -a filesToEat allSizes
  let 'no = 0' ||:
  filesToEat=()
  allSizes=($(getSize "${allFiles[@]}"))
  readonly -a allSizes
  # allSizes contains the sizes corrsponding to allFiles
  # filesToEat hold the constructed list of files to put on the current disk
  # no points to the next free position in filesToEat
  # totalSize should hold the sum of the sizes 
  #  of the files already put into filesToEat;
  #  it is set and reset externally.
  for p in "${allFiles[@]}"; do 
    if (( totalsize + ${allSizes[$(( no ))]} > $CDVOLUME )); then
	  eatFiles "${filesToEat[@]}"
          filesToEat=()
          finishCD
	  startTypedCD
	fi
      let "totalsize += ${allSizes[$(( no ))]}" ||:
      filesToEat[$(( no++ ))]="$p"
  done
  eatFiles "${filesToEat[@]}"
}

function eatFiles() {
    #{ oldIFS="$IFS"; IFS=$'\n'; echo "$FUNCNAME: args: " "$*" | head >&2;  IFS="$oldIFS"; }
    zeroDelimited "$@" | xargs -0 --no-run-if-empty \
	cp -s \
	--target-dir="$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"/ \
	--
}

function startTypedCD() {
#  set -x
  mkdir -p "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"
  start_action $" %s with %s" "$(( cdN ))" "$type"
#  set +x
}

function finishCD() {
#  set -x
  
  if [[ "$INDEX_FOR_APT" == yes ]]; then
      for arch in "${ARCH[@]}"; do
	  if [[ -d "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$arch" 
		|| "$arch" == "$ARCH" 
	  ]]; then
	      mkdir -p "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$arch/base"
	  fi
      done
      parse_localmirror_table | while read type mirror; do
	  mkdir -p "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"
      done
  fi
  mkdir -p "$THIS_LAYOUTS_DIR/cd$(( cdN ))"/.disk
  {
    ##################################
    #  DO NOT USE "@" synbol in the disk identfication string! -- it is not understood well by APT!
    echo -e "$CD_COLLECTION_TITLE; CD $(( cdN ))" \
      > "$THIS_LAYOUTS_DIR/cd$(( cdN ))"/.disk/info
    for f in "$THIS_BASES_DIR/$BASE"/plain-index-*; do
       [[ -f "$f" ]] && cp "$f" \
          "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$ARCH/base/full-$(basename "$f")"
    done
    #set -x
    other_archs_array=( 
	$(for arch in "${ARCH[@]}"; do
	    if [[ -d "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$arch/base" 
			&& "$arch" != "$ARCH" 
	       ]]; then
		echo "$arch"
	    fi
	    done)
    ) 
    # Debug:
    #echo "${other_archs_array[@]}"
    other_archs=""
    # Lav FIXME: missed in template now
    export other_archs # the parameter for php.
    if (( ${#other_archs[@]} > 1 )); then 
	other_archs="{$(IFS=','; echo "${other_archs_array[@]}";)}"
    else
	other_archs="$other_archs_array"
    fi
    # Debug:
    #echo "${other_archs}"
    #set +x
    for f in "$DISK_DESCRIPTION_SOURCE"*.template; do
	ext="${f%%.template}"
	ext="${ext##"$DISK_DESCRIPTION_SOURCE"}"
	title="$(< "$THIS_LAYOUTS_DIR/cd$(( cdN ))"/.disk/info)"
	sed -e < "$f" > "$THIS_LAYOUTS_DIR/cd$(( cdN ))"/index"$ext".html \
		"s|@TITLE@|$title|g
		 s|@PREFIX@|$PREFIX|g
		 s|@ARCH@|$ARCH|g"
    done
    for f in "$DISK_DESCRIPTION_SOURCE"*.css; do
	ext="${f%%.css}"
	ext="${ext##"$DISK_DESCRIPTION_SOURCE"}"
	cp -f "$f" "$THIS_LAYOUTS_DIR/cd$(( cdN ))"/disk-description"$ext".css
    done
  }
  let 'cdN++' ||:
  let 'totalsize = 0' ||:
#  set +x
  success
  return 0
}

function parse_localmirror_table() {
  if [[ ! "$TYPE_N_LOCALMIRROR_TABLE" ]]; then
    echo $"TYPE_N_LOCALMIRROR_TABLE empty; nothing to do! Set it before  \
calling again." >&2
    exit 1
  fi
  echo "$TYPE_N_LOCALMIRROR_TABLE" \
    | sed -e 's/^#.*$//' | sed -e '/^$/d'
}

function split() {
  enter_stage $"Splitting"
  printf $"(CDVOLUME=%s bytes are allocated for packages on each \
disk; you can change CDVOLUME parameter), disks:\n" "$CDVOLUME"

  echo D >&2
  let 'totalsize = 0' ||:
  let 'cdN = 1' ||:
  parse_localmirror_table \
      | while read type mirror || { type="$last_type"; finishCD; false; }; do
          startTypedCD
          splitMirrorDir "$type" || return 1
          last_type="$type"
        done
}

function fix_base() {
  enter_stage $"Fixing (\`snap-shooting')"
  if [[ ! "$BASE" ]]; then
    echo $"No base name specified." >&2
    exit 1
  fi

  parse_localmirror_table \
      | while read type mirror; 
  do
    start_action $"base-state %s for %s" "$BASE" "$type"
    mkdir -p "$(dirname "$THIS_BASES_DIR/$BASE/$type")" \
	&& cp -R --symbolic-link "$mirror" "$THIS_BASES_DIR/$BASE/$type" \
	&& success || failure
  done

  ( unset LANG{,UAGE}; export LANG{,UAGE}; \
    echo "$TASKNAME $(date +'%Y/%m (%b%d %H:%M%Z)')" > "$THIS_BASES_DIR/$BASE/title" \
  )
# the title will be like this: Sisyphus 2002/02: February, 13  23:21 MSK
}

function rm_base() {
    enter_stage $"Removing"
  if [[ ! "$BASE" ]]; then
    echo $"No base name specified." >&2
    exit 1
  fi
  if ! read \
    -p $"Do you really want to remove all data stored for base-state $BASE?[y/N]" \
    -r -n 1 \
  || [[ "$(echo "$REPLY" | tr [[:upper:]] [[:lower:]])" != y ]]; then
    echo
    return
  fi
  echo
  
  start_action $"all data associated with base state %s" "$BASE"
  rm -rf  "$THIS_BASES_DIR/$BASE" && success || failure
}

function ls_base() {
    enter_stage $"Listing base state"
  ls -d  "$THIS_BASES_DIR"/$BASE
}


# End of FUNCTIONS
#####################

mkdir -p "$D_WORKPLACE" \
&& case "$1" in

# Base states manipulation:
--fix-base)
  BASE="$2"
  fix_base
  makePlainIndex
  #set -x
  if [[ "$PUBLIC_SITE" ]]; then
      publishPlainIndex "$PUBLIC_SITE"/"$BASE"
  fi
  #set +x
  ;;
--rm-base)
  BASE="$2"
  if [[ "$BASE" == '*' ]]; then
    for d in "$THIS_BASES_DIR"/*; do
        BASE="$(basename "$d")"
        rm_base
    done
  else
    rm_base
  fi
  ;;
--ls-base)
  BASE="$2"
  ls_base
  ;;
--make-plain-index) # a remote destination can be given
  BASE="$2"
  if [[ "$BASE" == '*' ]]; then
    for d in "$THIS_BASES_DIR"/*; do
        BASE="$(basename "$d")"
        CREATE_PLAIN_INDEX=yes makePlainIndex
# 	if [[ "$3" && ! "$PUBLIC_SITE" ]]; then 
# 	    PUBLIC_SITE="$3"/"$BASE"
# 	fi
	if [[ "$PUBLIC_SITE" ]]; then
	    publishPlainIndex "$PUBLIC_SITE"/"$BASE"
	fi
    done
  else
    CREATE_PLAIN_INDEX=yes makePlainIndex 
#     if [[ "$3" && ! "$PUBLIC_SITE" ]]; then 
# 	PUBLIC_SITE="$3"
#     fi
    if [[ "$PUBLIC_SITE" ]]; then
	publishPlainIndex "$PUBLIC_SITE"/"$BASE"
    fi
  fi
  ;;

# Laying out the future CD filesystem:
--split-full) 
  BASE="$2"
  CD_COLLECTION_TITLE="Full $(<"$THIS_BASES_DIR/$BASE/title")"
  split
  ;;
--split-diff) 
  BASE="$2"
  DIFF_TO_BASE="$BASES_DIR/$3"
  CD_COLLECTION_TITLE="Diff $(<"$DIFF_TO_BASE/title") -> $(<"$THIS_BASES_DIR/$BASE/title")"
  split
  ;;
--index)
  #plain_index
  apt_index
  ;;
--clear)
  clearCDTrees
  ;;

# Operating with ISOs:
--mkiso)
  # List it before to check whether all link targets are there:
  list
  # Make the ISO(s):
  if [[ -z "$2" ]]; then
    echo $"Making all ISOs:"
    for cd in "$THIS_LAYOUTS_DIR"/cd[0-9]*; do [ -d "$cd" ] && mkiso "$(basename "$cd")"; done
  else
    mkiso cd"$2"
  fi
  ;;
--recorddvd)
  # List it before to check whether all link targets are there:
  list
  if [[ -z "$2" ]]; then
    echo $"Writing all DVDs:"
    for cd in "$THIS_LAYOUTS_DIR"/cd[0-9]*; do [ -d "$cd" ] && recorddvd "$(basename "$cd")" pause; done
  else
    recorddvd cd"$2" ""
  fi
  ;;
--recordcd)
  # List it before to check whether all link targets are there:
  list
  if [[ -z "$2" ]]; then
    echo $"Writing all CDs:"
    for cd in "$THIS_LAYOUTS_DIR"/cd[0-9]*; do [ -d "$cd" ] && recordcd "$(basename "$cd")" pause; done
  else
    recordcd cd"$2" ""
  fi
  ;;
--record)
  if [[ -z "$2" ]]; then
    echo $"Writing all ISOs:"
    for cd in "$THIS_ISOS_DIR"/cd*.iso; do record "$(basename "$cd")" pause; done
  else
    record cd"$2".iso
  fi
  ;;
--rmiso)
	enter_stage $"Removing disk images"
  if [[ -z "$2" ]]; then
    echo $"Removing all ISOs..."
    for cd in "$THIS_ISOS_DIR"/cd*.iso; do 
	action "$(basename "$cd")" "rm -f '$cd'"
    done
  else
    action cd"$2".iso "rm -f '$THIS_ISOS_DIR/cd$2.iso'"
  fi
  ;;

--lsiso)
	enter_stage $"Listing disk images"
    du --human-readable --summarize "$THIS_ISOS_DIR"/cd*.iso
    du --summarize --bytes "$THIS_ISOS_DIR"/cd*.iso
  ;;

# List the sizes of objects:
--list)
  list
  ;;

# A short command to pass the layouting stage
--LAYOUT-FULL)
  BASE="$2"
  CD_COLLECTION_TITLE="Full $(<"$THIS_BASES_DIR/$BASE/title")"
  clearCDTrees
  split \
    && apt_index \
    && echo $"Successfully laid out the future disks, listing:" \
    && list \
    && printf $"\nNow you can move around and copy links and \
files in %s as you like. When creating the images of the disks, all the links \
will be replaced by real files.\n" "$THIS_LAYOUTS_DIR" \
    || { echo $"Failed to lay out"; exit 1; }
  ;;

--LAYOUT-DIFF)
  if [[ ! "$2" || ! "$3" ]]; then
      echo $"Error: No dest or src base!" >&2
      usage
      exit 1
  fi
  BASE="$2"
  DIFF_TO_BASE="$BASES_DIR/$3"
  CD_COLLECTION_TITLE="Diff $(<"$DIFF_TO_BASE/title") -> $(<"$THIS_BASES_DIR/$BASE/title")"
  clearCDTrees
  split \
    && apt_index \
    && echo $"Successfully laid out the future disks, listing:" \
    && list \
    && printf $"\nNow you can move around and copy links and \
files in %s as you like. When creating the images of the disks, all the links \
will be replaced by real files.\n" "$THIS_LAYOUTS_DIR" \
    || { echo $"Failed to lay out"; exit 1; }
  ;;

*)
  usage
  exit 1 
  ;;
esac
