Wednesday, July 29, 2009

bak2disc-tpb

#!/bin/bash
################################################################################
#
# bak2disc - backup to disc volumes while maintaining original data structure
# Copyright (C) 2005-2006 dorphell <dorphell [AT] gmail [DOT] 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 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
################################################################################

#--------------------------------------------------------------------
# PUBLIC CONSTANTS [Default Parameters]
#--------------------------------------------------------------------

## Dir used for meta-data (a few MBs of ASCII files) ##
TMP_DIR="${TEMP:-/tmp/bak2disc}"
mkdir -p $TMP_DIR

## Disc type (valid types: CD74, CD80, DVD5, DVD9, DLT7, LTO2, LTO3) ##
MEDIA_TYPE="DVD5"

## Space allocated for index metadata (MiB)##
INDEX_SIZE=4

## mkisofs options used by growisofs ##
MKISOFS_OPTS=" -r -J "

## Limit for the volume-filling search in the element array ##
SEARCH_RANGE=150

Label="Volume"


#--------------------------------------------------------------------
# PRIVATE CONSTANTS [Do not Modify]
#--------------------------------------------------------------------

VERSION="0.7-tpb"
PROG_NAME="bak2disc"
GB=1073741824         # 1024^3
MB=1048576            # 1024^2

## ANSI Color modifiers ##
C_BOLD="\e[1m"        # Bold
C_NORM="\e[m\e[0;39m" # Normal
C_BLUE="\e[1;34m"     # Blue
C_RED="\e[1;31m"      # Red

## Only use newlines for field separation ##
IFS=$'\n'

## Error Messages ##
ErrorMsgs=(
"00: No error"
"01: Unknown error"
"02: Program \'\$2\' not in \\\$PATH"
"03: Working directory \'\$2\' already exists"
"04: Permission denied or file/directory does not exist: \$2"
"05: File[s] too big, choose a higher-capacity disc or suppress oversized file[s]"
"06: Temporary directory \'\$2\' exists"
"07: Could not create temporary directory \'\$2\'"
"08: User reported failed burn for disc volume \$2, --resume to try again"
"09: Option \'\$2\' requires an argument"
"10: Invalid option: \$2"
"11: Previous session metadata not found. Incorrect --temp parameter?"
"12: No options given, what do you want to do?"
"13: No \'--include\' files/directories defined"
"14: Disc \$2 index larger than allocated space, increase --index size"
"15: Please provide a single valid operating mode"
"16: Unknown disc type \'\$2\'. Valid types: cD74, CD80, DVD5, DVD9, DLT7, LTO2, LTO3"
"17: SIGINT caught"
"18: Invalid --speed argument, value must be an integer"
"19: mkisofs is really genisoimage which will not work")

#--------------------------------------------------------------------
# FUNCTIONS
#--------------------------------------------------------------------

Help() {
cat << EOF
$PROG_NAME v$VERSION (c) 2006 dorphell
Usage: $PROG_NAME <mode> [options]
Backup to disc volumes while maintaining original data structure

    Informative flags:
    -h, --help      Print help screen
    -v, --version   Print version information

    Operating mode flags: (one required)
    -f, --fresh     Start a new backup session
    -n, --noburn    Generate backup volume information but don't burn to disc
    -r, --resume    Resume the burn phase of a previous backup session (relies
                    on original metadata)
    -w, --wipe      Clean up temporary directory and exit

    Accessibility flags:
    -j, --eject     Open drive tray for disc loading/unloading and skip the
                    verification prompt.
    -l, --exlarge   Automatically exclude files too large for backup media
    -c, --nocolor   Turn off all text color/highlighting

    Configuration options: (every option requires an argument)
    -i, --include   Colon-separated file/directory paths to include (multiple
                    definitions and bash globbing wildcards are also vallid)
    -e, --exclude   File/directory paths to exclude (same syntax as --include)
    -t, --temp      Temporary directory [default: $TMP_DIR]
    -o, --mkisofs   Burning options passed onto mkisofs/growisofs
                    [default: $MKISOFS_OPTS]
    -m, --mtype     Disc type (CD74, CD80, DVD5, DVD9, DLT7, LTO2, LTO3) [default: $MEDIA_TYPE]
    -x, --index     Space allocated for index metadata out of disc capacity
                    [default: $INDEX_SIZE]
    -g, --range     Number of files/directories to poll when filling slack
                    space on volumes [default: $SEARCH_RANGE]
    -s, --speed     Speed factor of the writing process (integer value)
                    [default: Maximum supported by the drive]
    --label         Label the .iso and volume name

    Examples:
    $PROG_NAME --fresh --include "/etc:/mnt/HD*:/opt" -d /dev/hdc --mtype DVD5
    $PROG_NAME -n -i /usr -i /var -e /var/log --temp /tmp --range 10 -m DVD5
    $PROG_NAME --resume --temp /var/tmp

    GNUtar:
    gtar cf <tape> -T $TMP_DIR/Volume_1

EOF
}

Version() {
   echo "$PROG_NAME $VERSION"
}

## ErrorExit "int error_code" "string reference_name" ##
ErrorExit() {
   echo -ne "$C_BLUE" >&2
   echo -ne ">> "
   echo -ne "$C_RED" >&2
   eval echo -n "ERROR ${ErrorMsgs[$1]}"
   echo -ne "$C_NORM" >&2
   echo
   exit $1
}

## PrintBold "string message" ##
PrintBold() {
   echo -ne "$C_BOLD" >&2
   echo -ne "$1"
   echo -ne "$C_NORM" >&2
}

## PrintStatus "string message" ##
PrintStatus() {
   echo -ne "$C_BLUE" >&2
   echo -ne ">>"
   echo -ne "$C_NORM$C_BOLD " >&2
   echo -ne "$1"
   echo -ne "$C_NORM" >&2
   echo
}

## YesNo "string question" "boolean default" [y/n] ##
YesNo() {
   OPTIONS="[y/n]"
   [ "$2" == "y" ] && OPTIONS="[Y/n] "
   [ "$2" == "n" ] && OPTIONS="[y/N] "

   PrintBold "$1 $OPTIONS"
   IFS=$' \t\n' read ANSWER
   [ ! "$ANSWER" ] && [ "$2" ] && ANSWER="$2"
   while [ "$ANSWER" != 'y' ] && [ "$ANSWER" != 'n' ] && [ "$ANSWER" != 'Y' ] && [ "$ANSWER" != 'N' ]; do
      PrintBold "Invalid response, please answer 'y' or 'n': "
      IFS=$' \t\n' read ANSWER
   done
   [[ "$ANSWER" == 'y' || "$ANSWER" == 'Y' ]] && return 0 || return 1
}

## CheckPath "string program_name" ##
CheckPath() {
   while [ "$1" ]; do
      ! type "$1" &>/dev/null && ErrorExit 2 "$1"
      shift
   done
}

## ParseArgs "string args" (e.g. "$@") ##
ParseArgs() {
   while [ "$1" ]; do
   ## Informative flags ##
      case "$1" in
         -h|--help)
            Help;                  exit ;;
         -v|--version)
            Version;               exit ;;
   ## Operating modes flags ##
         -f|--fresh)
            FRESH=1;              shift ;;
         -r|--resume)
            RESUME=1;             shift ;;
         -n|--noburn)
            NOBURN=1;             shift ;;
         -w|--wipe)
            WIPE=1;               shift ;;
   ## Accessibility flags ##
         -j|--eject)
            CheckPath 'eject'
            EJECT=1;              shift ;;
         -l|--exlarge)
            AUTO_EXCLUDE=1;       shift ;;
         -c|--nocolor)
            NOCOLOR=1;            shift ;;
   ## Configuration options ##
         -i|--include)
            [ ! "$2" ] && ErrorExit 9 "$1"
          INCLUDE="$INCLUDE:$2"; shift 2;;
         -e|--exclude)
            [ ! "$2" ] && ErrorExit 9 "$1"
          EXCLUDE="$EXCLUDE:$2"; shift 2;;
         -o|--mkisofs)
            [ ! "$2" ] && ErrorExit 9 "$1"
            MKISOFS_OPTS="$2";   shift 2;;
         -m|--mtype)
            [ ! "$2" ] && ErrorExit 9 "$1"
            MEDIA_TYPE="$2";     shift 2;;
         -x|--index)
            [ ! "$2" ] && ErrorExit 9 "$1"
            INDEX_SIZE="$2";     shift 2;;
         -t|--temp)
            [ ! "$2" ] && ErrorExit 9 "$1"
            TMP_DIR="$2";        shift 2;;
         -g|--range)
            [ ! "$2" ] && ErrorExit 9 "$1"
            SEARCH_RANGE="$2";   shift 2;;
         -s|--speed)
            [ ! "$2" ] && ErrorExit 9 "$1"
      [ "${2##*[^0-9]*}" ] || ErrorExit 18
            SPEED="-speed=$2";   shift 2;;
         --label)
             [ ! "$2" ] && ErrorExit 9 "$1"
             Label="$2"; shift 2;;
         *)
            Help;      ErrorExit 10 "$1";;
      esac
   done
}


## SetMediaSize "string preset" (e.g. SetMediaSize "dvd9") ##
SetMediaSize() {
   MEDIA_TYPE="$(echo $1| tr a-z A-Z)"
   case "$MEDIA_TYPE" in
      CD74) MEDIA_SIZE=650  ;;
      CD80) MEDIA_SIZE=703  ;;
      DVD5) MEDIA_SIZE=4482 ;;
      DVD9) MEDIA_SIZE=8144 ;;
      DLT7) MEDIA_SIZE=30000 ;;
      LTO2) MEDIA_SIZE=190000 ;;
      LTO2c) MEDIA_SIZE=323000 ;;
      LTO3) MEDIA_SIZE=390000;;
      LTO3c) MEDIA_SIZE=663000;;
         *) Help;
            ErrorExit 16 "$1" ;;
   esac
   let "MEDIA_SIZE-=INDEX_SIZE"
}


## ValidatePath "string path" "string output_variable" ##
## If path is readable, return its proper pathname; else error + exit ##
ValidatePath() {
   [ ! -r "$1" ] && CleanUp && ErrorExit 4 "$1"
   [ "$1" == '/' ] && return
   eval "$2=\"$(echo "$1"| sed 's|/\+|/|g;s|/$||')\""
}

## GetList "string du_output_syntax" (e.g. "1234 <TAB> /path/to/dir") ##
## Returns a list of files/dirs that are less than media size ##
GetList() {
   ## Set Size and Name ##
   VARS=(${1//$'\t'/$'\n'})
   Size=${VARS[0]}
   Name=${VARS[1]}
   if [ $Size -gt $((MEDIA_SIZE*MB)) ] && [ -d "$Name" ]; then
      for x in $($DU -ba --max-depth=1 "$Name"| gawk -F '\t' '$2 != "'$Name'" {print}'); do
         GetList "$x"
      done
   else
      printf "%s\t%s\n" "$Name" "$Size"
   fi
}

## Burn disc volumes defined in TMP_DIR ##
BurnVolumes() {
   NumOfDiscs=$(head -n 1 "$TMP_DIR/summary" 2>/dev/null| gawk '{print $8}')
   MEDIA_TYPE=$(head -n 1 "$TMP_DIR/summary" 2>/dev/null| gawk '{print $9}')

   ## Check if metadata exists ##
   [ ! $NumOfDiscs ] && ErrorExit 11

   ## If --resume, reprint distribution summary ##
   if [ $RESUME ]; then
      PrintStatus "Resuming burn session..."
      while read line; do
         vol=$(echo $line| gawk '$1 == "Disc" {printf "%i\n", $2}')
         [ ${line:0:1} == " " ] && echo "$line"        && continue
         [ ${line:0:1} == "I" ] && PrintStatus "$line" && continue
         [ -z $vol ] && PrintBold "$line\n" && continue
         [ -e "$TMP_DIR/Volume_$vol" ] && PrintBold "$line\n" || echo "$line [Done]"
      done < "$TMP_DIR/summary"
      echo
   fi


## Make ISO files
#   for (( DISC_NUM=1; DISC_NUM<=$NumOfDiscs; DISC_NUM++ )); do
   DISC_NUM=1
   while [ $DISC_NUM -le $NumOfDiscs ]; do
      ## Volume already burned ##
      [ ! -e "$TMP_DIR/Volume_$DISC_NUM" ] && continue


      ## Burn the volume ##
      ALL_MKISOFS_OPTS="$MKISOFS_OPTS -quiet -V ${Label}-${DISC_NUM}_of_$NumOfDiscs -graft-points -exclude-list $EXC_LIST -path-list $TMP_DIR/Volume_$DISC_NUM  -o ${Label}_$DISC_NUM.iso"

      IFS=$' \n';
      echo "mkisofs $ALL_MKISOFS_OPTS"
      mkisofs $ALL_MKISOFS_OPTS
      IFS=$'\n'

      ## Volume successfully burned, delete volume metadata ##
      rm "$TMP_DIR/Volume_$DISC_NUM" "$TMP_DIR/volume_${DISC_NUM}_of_${NumOfDiscs}"
      DISC_NUM=$(expr $DISC_NUM + 1)
   done

   ## Backup complete, wipe out metadata ##
   PrintStatus "All $NumOfDiscs backup volumes burned."
   CleanUp
}

## CleanUp (removes "$TMP_DIR" entirely) ##
CleanUp() {
   PrintStatus "Cleaning up temporary data"
   rm -rf "$TMP_DIR"
}

Terminate() {
   echo
   ErrorExit 17
}

#--------------------------------------------------------------------
# SCRIPT START
#--------------------------------------------------------------------

## Capture ^C exit code ##
trap Terminate SIGINT

## Check if utilities are in $PATH ##
# Solaris /bin/du won't work.  Look for GNU du named gdu
[ -f $(which gdu) ] && DU=$(which gdu) || DU=$(which du)
[ $(uname -s) == "SunOS" -a $(basename $DU) != "gdu" ] && ErrorExit 2 "gdu"

CheckPath 'gawk' 'sed' 'printf' 'sort' "$DU" 'stat' 'comm' 'mkisofs'

mkisofs --version | grep -s genisoimage > /dev/null
[ $? -eq 0 ] && echo "mkisofs is really genisoimage which will not work"

## Parse command-line arguments ##
[ -z "$1" ] && Help && ErrorExit 12
ParseArgs "$@"

ValidatePath "$TMP_DIR" "TMP_DIR"

TMP_DIR="$TMP_DIR/$PROG_NAME-$USER"
FILE_LIST="$TMP_DIR/include"
EXC_LIST="$TMP_DIR/exclude"

SetMediaSize "$MEDIA_TYPE"

[ $NOCOLOR ] && unset C_BOLD C_NORM C_BLUE C_RED

## No operating modes defined, error ##
[ $((FRESH+NOBURN+RESUME+WIPE)) -ne 1 ] && Help && ErrorExit 15

## If --resume specified, just burn using preexisting metadata ##
[ $RESUME ] && BurnVolumes && exit

## Delete any garbage/leftovers and start fresh ##
[ $WIPE ] && CleanUp && exit

## Check for leftovers, if not clean, ask what to do ##
if [ -e "$TMP_DIR" ]; then
   ! YesNo "Leftover metadata detected; DELETE and start fresh?" "n" && PrintBold "Nothing dnoe... exiting\n" && exit
   CleanUp
fi

## Check/Ask for required variables ##
[ ! "$INCLUDE" ] && Help && ErrorExit 13

PrintBold "$C_RED-------------------------------------------------------------------$C_NORM\n"
PrintBold " Do not modify included files until they have been written to disc\n"
PrintBold " Excessive growth may cause volume to overflow disc capacity\n"
PrintBold "$C_RED-------------------------------------------------------------------$C_NORM\n"

## Start a fresh backup now ##
mkdir -p "$TMP_DIR" || ErrorExit 7 "$TMP_DIR"

## Convert path lists to arrays ##
INCLUDE=(${INCLUDE//:/$'\n'})
EXCLUDE=(${EXCLUDE//:/$'\n'})

## Validate all dir/file paths ##
x=0
while [ $x -lt ${#INCLUDE[@]} ]; do ValidatePath "${INCLUDE[$x]}" "INCLUDE[$x]"; x=$(expr $x + 1); done
x=0
while [ $x -lt ${#EXCLUDE[@]} ]; do ValidatePath "${EXCLUDE[$x]}" "EXCLUDE[$x]"; x=$(expr $x + 1); done

## Remove all dupes ##
for x in "${INCLUDE[@]}"; do INCLUDE=($(echo "${INCLUDE[*]}"| sed "\|^$x/|d"| sort -u)); done
for x in "${EXCLUDE[@]}"; do EXCLUDE=($(echo "${EXCLUDE[*]}"| sed "\|^$x/|d"| sort -u)); done

## Generate element filelist from INCLUDE paths ##
for x in "${INCLUDE[@]}"; do
   GetList "$($DU -bs "$x")"
done| sort > "$FILE_LIST"

## Remove excluded items from filelist ##
if [ "${EXCLUDE[*]}" ]; then
   for item in $($DU -bs "${EXCLUDE[@]}"); do
      item=(${item//$'\t'/$'\n'})
      Size=${item[0]}
      Name=${item[1]}

      ## *Exact* match ##
      Exists=$(gawk -F '\t' '$1 == "'$Name'" || $1 ~ "'$Name'/" {print $1}' "$FILE_LIST")

      if [ "$Exists" ]; then
         for x in $Exists; do sed "\|^$x\t.*|d" -i "$FILE_LIST"; done
      else
         Parent=""
         while [ ! "$Parent" ] && [ "$Name" ]; do
            Name="${Name%/*}"
            Parent=$(gawk -F '\t' '$1 == "'$Name'" {print}' "$FILE_LIST")
         done
         Parent=(${Parent//$'\t'/$'\n'})
         PName=${Parent[0]}
         PSize=${Parent[1]}
         NewSize=$((PSize-Size))
         sed "s|\(^$PName\)\t\(.*$\)|\1\t$NewSize|" -i "$FILE_LIST"
      fi
   done
fi

## Initialize element array ##
ELEMENTS=($(cat "$FILE_LIST"))

## Fill content elements into disc volumes ##
Current=0; Count=0; CountRange=0; declare -i Size; LastElement=${#ELEMENTS[@]}

while [ "${ELEMENTS[*]}" ]; do
   line=(${ELEMENTS[$Count]//$'\t'/$'\n'})
   Name="${line[0]}" # File name
   Size="${line[1]}" # File size

   if [ $Size -gt $((MEDIA_SIZE*MB)) ]; then                      # file bigger than disc capacity
      AEXCLUDE[${#AEXCLUDE[@]}]="$Name"                           # add file to auto-exclude list
#      unset ELEMENTS[$Count] Name
      unset ELEMENTS[$Count]
      unset Name
   fi

   if [ ! $Name ] && [ $Count -ne $CountRange ]; then             # skip null elements
      :
   elif [ $CountRange -ne 0 ] && [ $Count -eq $CountRange ]; then # end of search range, volume filled
      CountRange=0
      let "Count=KeepCount-1"                                     # go to 1st element that didn't fit
      let "Current++"
#      echo "$Current"
#      echo "$((VolSizes[$Current]+Size))  $((MEDIA_SIZE*MB))"

   elif [ $((VolSizes[$Current]+Size)) -lt $((MEDIA_SIZE*MB)) ]; then
      VolSizes[$Current]=$((${VolSizes[$Current]}+$Size))
      VolNames[$Current]="${VolNames[$Current]}$Name\n"
      unset ELEMENTS[$Count]
   else                                                           # element too big for current vol
      if [ $CountRange -eq 0 ]; then                              # remember element position
         KeepCount=$Count
         let "CountRange=Count+SEARCH_RANGE"
         if [ $CountRange -gt $LastElement ]; then                # don't set range outside the array!
            CountRange=$LastElement
         fi
      fi
   fi
   let "Count++"
done

## Ask if big files should be auto-excluded ##
if [ "$AEXCLUDE" ]; then
   if [ ! $AUTO_EXCLUDE ]; then
      for x in "${AEXCLUDE[@]}"; do echo "| $x"; done
      YesNo "+ The preceding file[s] will not fit on a $MEDIA_TYPE disc, exclude and continue?" "y" || ErrorExit 5
      echo
   fi
   EXCLUDE=("${EXCLUDE[@]}" "${AEXCLUDE[@]}")
fi

## Account for excluded files in distribution summary ##
find "${EXCLUDE[@]}" ! -type d 2>/dev/null| sort > "$EXC_LIST"

## Generate index and filelists ##
x=0;
while [ $x -lt ${#VolNames[@]} ]; do
   BurnName="Volume_$((x+1))"                      # e.g. Volume_1
   IndexName="volume_$((x+1))_of_${#VolNames[@]}"  # e.g. volume_1_of_16

   ## Generate mkisofs graft-points formated filelist ##
   echo -e "${VolNames[$x]}"| gawk -F '\n' '$1 {printf "data%s=%s\n", $1, $1}' > "$TMP_DIR/$BurnName"

   ## Generate volume reference index ##
   VolFiles[$x]=$(comm -23 <(find $(echo -e "${VolNames[$x]}") ! -type d 2>/dev/null| sort) "$EXC_LIST")
   echo "${VolFiles[$x]}" > "$TMP_DIR/$IndexName"

   ## Generate master catalog ##
   echo "### $IndexName ###" >> "$TMP_DIR/catalog.idx"
   echo -e "${VolFiles[$x]}\n"| sed 's|^/|'$((x+1))': /|' >> "$TMP_DIR/catalog.idx"

   ## Append reference index and master catalog to mkisofs input filelist ##
   #echo "$IndexName=$TMP_DIR/$IndexName" >> "$TMP_DIR/$BurnName"
   echo "catalog.idx=$TMP_DIR/catalog.idx" >> "$TMP_DIR/$BurnName"
   x=$(expr $x + 1 )
done

## Generate mkisofs exclude list file ##
echo "${EXCLUDE[*]}" > "$EXC_LIST"

TotalSize=$(echo "${VolSizes[*]}"| gawk '{sum+=$1} END {printf "%.2f", (sum/'$GB') }')
Index_Size=$(stat -c %s "$TMP_DIR/catalog.idx" 2>/dev/null)
TotalFiles=$(echo "${VolFiles[*]}"| grep -c "[^\n]")
NumOfDiscs=${#VolNames[@]}

## Overall backup summary ##
SUM_TOTALS=$(printf "Summary: %s files (%s GiB) divided into %s %s disc volumes" "$TotalFiles" "$TotalSize" "$NumOfDiscs" "$MEDIA_TYPE")
SUM_INC=$(for x in "${INCLUDE[@]}"; do echo "   $x"; done)
SUM_EXC=$(for x in "${EXCLUDE[@]}"; do echo "   $x"; done)

PrintBold "$SUM_TOTALS\n"; echo -e "$SUM_TOTALS" > "$TMP_DIR/summary"
                PrintStatus "Items Included:" && echo "$SUM_INC" && echo -e "Items Included:\n$SUM_INC" >> "$TMP_DIR/summary"
[ $EXCLUDE ] && PrintStatus "Items Excluded:" && echo "$SUM_EXC" && echo -e "Items Excluded:\n$SUM_EXC" >> "$TMP_DIR/summary"

## Disc volume distribution summary ##
x=0
while [ $x -lt $NumOfDiscs ]; do
   DISC_NUM=$((x+1))
   DISC_FILES=$(echo "${VolFiles[$x]}"| grep -c "[^\n]")
   VIDX_SIZE=$(stat -c %s "$TMP_DIR/volume_${DISC_NUM}_of_${NumOfDiscs}")
   DISC_SIZE=$(echo "${VolSizes[$x]}" $Index_Size $VIDX_SIZE $MB| gawk '{printf "%f", ($1+$2+$3)/$4 }')
   DISC_FREE=$(echo $MEDIA_SIZE $INDEX_SIZE $DISC_SIZE| gawk '{printf "%i", $1+$2-$3}')
   PRINT_DIST="$PRINT_DIST$(printf "Disc %3d: %5d files -> %8.2f MiB, %4i MiB free" "$DISC_NUM" "$DISC_FILES" "$DISC_SIZE" "$DISC_FREE")\n"
   [ $DISC_FREE -lt 1 ] && ErrorExit 14 $DISC_NUM
   x=$(expr $x + 1 )
done; PrintBold $PRINT_DIST

## Save distribution summary ##
echo -ne $PRINT_DIST >> "$TMP_DIR/summary"

## If --noburn specified, exit now ##
[ $NOBURN ] && PrintStatus "Backup metadata generated." && exit

## Ask whether to burn now or leave metadata for later ##
YesNo "\nBegin burn sequence now?" "y" && BurnVolumes

#--------------------------------------------------------------------
# SCRIPT END
#--------------------------------------------------------------------

Archiving a tree to DVD with bak2disc

bak2disc by dorphell is a script to create DVDs of a directory tree, preserving the structure. It will create an index file on the DVD so you can find which DVD the files are on.

I modified the script to create .iso files. You can also feed the file lists created to tar. So I modified the script to create lists contining X GB of data. I feed that to tar and can tar files to a DLT, SDLT, LTO, etc tape.

I sent an email and didn't get a reply. I'll try to get a version up somewhere.

Wednesday, June 11, 2008

Micro Irrigation

Article showing typical layout

HOWTO install a system
From Colorado State

RainBird automation
Tutorials
Irrigo systems - nice picture
Drip Works - end user vendor
Toro Micro Irrigation

Wednesday, June 4, 2008

Gardening

Just a few quick sites

Getting rid of weeds from http://www.thisgardenisillegal.com

Composting

http://myfolia.com/ is a garden tracking journal

According to http://www.garden.org, We are Zone 6a. And they have a nice plant zone finder.

Oh My!! GardenMon

Friday, February 8, 2008

Unsolicited phone calls


From Slashdot:


Here is an effective (though laborious) way to deal with that.



1. Register on National Do-Not-Call list.

2. Wait 3 month beginning period.

3. Get caller ID.

4. Wait for another call.

5. Be pleasant to the person, if you can order something cheap, say $10, do it.

6. Get their address and phone number as you place the order.

7. Photograph the Caller ID display as evidence.

8. Take good notes including date, time, person talked to, company name, as more evidence.

9. Copy the bill you receive for $10 as conclusive evidence of marketing intent.

10. Go to your county courthouse, lodge a small claim for $500 for a telemarketing violation.

11. Send them proper notice they are being sued.

12. Since they are often out of state, they won't show and you get default judgment.

13. If they do show, you have proof of listing, notice, call, and call purpose.

14. For bonus dollars, ($500 per item) look into whether they have,
train to, practice and publish upon demand the required company calling
policies.

15. Profit!!!



Powered by ScribeFire.

Sunday, February 3, 2008

Podcasting with an M-Audio Microtrack

Think of the MT (Microtrack) as you would a cassette recorder. Don't worry about listening. That's for later on the computer. The MT is to capture the sound. Much you'd use a camera to take lots of pictures of an event. You'll select relevant photos for your presentation, possibly editing and cleaning them up. You might change the order of the photos to better fit your presentation.


Make sure the battery is charged up.

Use the electret microphone on the wire, not the T mic. This lets you put the microphone near the sound.

On one side of the MT (microtrack) is a L/M/H switch. Put it on M for Microphone.
Put the microphone in its jack.

Press record to start recording. Do a test recording to check sound levels.

On the screen you'll see 2 bars, labeled L and R, for Left and Right.
The levels are the sensitivity of the sound. You want to adjust the level or sensitivity of the microphone so you get all the sound but not so sensitive that the sound is distorted.

Too low a level & the mic will not pickup (hear) any sound. Too high a level and there will be too much static and distortion. You might be able to hear someone's heartbeat ;-)

You want to adjust the levels so they are near the right side, but not hitting the end. Read your script while doing this until the levels are adjusted.

Hit record again to stop the recording. The MT will display "Writing" as it saves the recording as an MP3 file.

I'd suggest keeping a list of what you record:
  1. Sound check
  2. Script take #1
  3. Script take #2
  4. recording of speech to students
The 1st MP3 file on the MT is 1), etc...

Now that you've recorded, bring the MT over to the computer. Plug the USB cable into the MT and the computer.

The MT will appear as a disk called "MICRODRIVE" with all your recordings named file001.mp3, file002.mp3, etc. There is one for each time you pressed rec to start & then rec to end. Your log will help you figure out which is which.

They might not start number 001, but they will be numbered sequentially.

Create a folder in your My Documents folder and copy the files from the MICRODRIVE into it. Do not erase them from the MICRODRIVE yet. These are your originals. Some people make a CD of them so they have a read only copy.

Now you have your sound files to edit. MT supplies Audacity which is a good choice. You can copy it from the CD or download it off the internet.

I've never used Audacity, but basically it lets you edit your sound files. You can filter it to change the sound if your original recording wasn't optimal. Mostly, you'll be cutting out dead air and splicing snippets into place. You might find splicing the 1st half of take #2 with the 2nd half of take #3 to be the best choice. The better your original, the less to do latter.

Thursday, December 27, 2007

TiVo

Tivo Desktop
Running on Windows. Grabs the shows to a local VM disk. It won't save to a mapped drive. Lame.

VideoReDo
Converts the .TiVo to an mpg that the TiVo can view. Also strips commercials. Costs about $30. Windows.

TVAP
Watches the local VM disk for new shows. It then runs VideoReDo on the shows and saves them to the file server. It creates a text file with the show metadata in it. Windows.

pyTiVo
Publishes the shows on the file server for the TiVo to view again.

Runs on the file server (Solaris) but could be anything that can run python and ffmpeg (mplayer?). Unix, MacOSX, Windows. Uses the show metadata from TVAP.

Apparently pyTivo doesn't like "-" in the name of a share. Anything I tried to transfer from such a share would pause after 10 seconds and eventually kill the transfer. Removing the "-" from the sharename fixes it.

pyTivo has a version that will create a share for every subfolder you have. It works well.



Encoding and Ripping

Lifehacker Top 10: Top 10 Free Video Rippers, Encoders, and Converters

DIY tools

Organizing blogs

I've been reading a number of blogs about organizing, GTD (Getting Things Done) and dealing with clutter. Here's a list of links:

Unclutterer - and dealing with paper clutter
Declutter an area from Good housekeeping

43 Folders deals with GTD and is a source of the hipster PDA (paper based)
Lifehacker has tips on GTD and changing your life habits
Zen Habit's simplification of GTD Nice graphic of flow

Resources for Decluttering your home from http://zenhabits.net

WWLD? No. 2: Keeping Connected | 43 Folders
WWLD? No. 3: Organizing your environment | 43 Folders
WWLD? No. 4: Living Your Life | 43 Folders

Powered by ScribeFire.

Wednesday, December 5, 2007

Sysadmin

Brendan Gregg has a ton of Solaris tools and stuff that works elsewhere. Portping can substitute where pings are not returned but another service is.


Clonezilla is a ghost clone

How to Make a Network Cable

Friday, October 26, 2007

Macintosh software

XStrimmer - trim universal binaries & other languages from apps
Macports - Free software for Mac. was darwinports

More SCSI issues

I got another card with an LSI 53c895. It should be supported by the glm driver in 64 bit solaris. *sigh* Nope.

So now, I've got the Adaptec 2940au in the Linux box. It's seen but not the tape drive. I have a 2940U2 card to try also.

When did SCSI get difficult?

Friday, October 19, 2007

Solaris 10 x86 SCSI cards

I just got a cast off DLT 7000 drive to put in my Solaris x86 box. I've had SCSI on most of my systems dating back to a 286 with an Adaptec 1542b card. I ran Minix and DOS at the time with a Syquest 88MB drive. I often used that drive on Macintosh at a nearby college to download from simtel20.

Later I got a 486 and a 2GB 4mm DAT drive. I used the same card. The syquest and DAT worked with versions of tar for MacOS 7, SunOS 4.x, OS/2, DOS and Linux. Back when my drives were < 500MB, 2GB was quite large.

Along the way, I picked up other SCSI adapters; they were new or salvaged. I have a Symbios 53c825, an Adaptec 2940u, a Compaq Ultra 2. I thought they'd work great in my AMD dual core Solaris box.

*sigh*. The Symbios uses the ncrs driver which is 32 bit only. The 2940 uses the adp which is only 32bit. The Compaq uses the cadp which is also only 32 bit. They will work in Linux and probably MacOSX. I've found some stuff on the net about the adp and cadp drivers having issues and the glm driver for LSI chips is in house. The glm driver is the only one of the lot to be 64 bits.

So, for Solaris, I need a new card. The best bet I think is an LSI (ne symbios) card that uses the glm driver. The 53c825 isn't supported by glm, but many older/newer ones are. Sun used LSI chips. Some of the LSI cards are much cheaper then the Adaptec (the adp320 driver will work) I would need to get. I'm getting one on eBay at 1/3 the price of the Adaptecs that would work. More information later.

I could run backups on my Linux box, but I want to have the drive local to the disks, etc. Gigabit is about 60 MB/s and the drive is 8 (5?) MB/s so there's no lack of bandwidth, but all the data is on the Solaris file server.

Tuesday, September 11, 2007

Today's links

DVD Flick - DVD Authoring w/ chapterising
Libra - Personal media inventory w/ lending. Barcode reading via webcam, cuecat
ZFS quick guide.
Automatic ZFS snapshotting - Not quite Netapp, but better then anything else out there IMO

Friday, September 7, 2007

Podcasting links

A friend of my wife is interested in starting podcasting so I googled some links.
She has a Macintosh so I'm focusing on Mac tools

O'Reilly Digital Media Article - a good site for all digital media
Audacity is a good audio editor. It's cross platform and open source too. Free.
Engadget has a HOWTO that mentions free tools for receiving and creating
A Linux setup for Recording Video - many of the tools are available for MacOSX
From about.com - more info
iLounge is a cool iPod site with a Beginner's Guide

Most of these articles mention Free/Open Source software. You don't need to buy software to podcast. You can get by with a microphone, a computer, free software and maybe a webcam. Better microphones and a digital camcorder are obvious upgrades.

There might be Legal issues. This is the Creative Commons take on it. It's based on EFF's Guide. The EFF is all about protecting your legal rights in digital media. Since you're publishing, you care about Intellectual Property (IP) and Copyright. Lawrence Lessig is worth paying attention to.

I'd focus on creating the content, the audio MP3. I'd leave distribution to one of the podcasting sites. The about.com link has some sites.

Fly Fusion Pen

My wife & I got a Fly Fusion pen to try. You write on special paper and the pen records your writing.

You can hook it up to a USB cable and import the drawing & writing. Output is an image or OCR will transform the text into a document.

In addition, the pen had a speaker and headphone jack to play music and give feedback for other functions. There is a music playing app: You draw a keyboard and can then play it with the pen. It will translate spanish writing to english speaking or vice versa. A calculator.

Your lettering needs to be block style like elementry school. I have pretty bad handwriting and decent block lettering. The OCR works well for me.

It's targeted at kids for homework and the applications are geared that way. For note taking, I think it's good for business use too. It's less obtrusive in a meeting then a laptop or especially a table computer. It's quicker then using a Palm. It captures drawings better then both. At $70 plus $8 per notebook, it's a nobrainer.

Wednesday, September 5, 2007

pyTivo issues

Apparently pyTivo doesn't like "-" in the name of a share. Anything I tried to transfer from such a share would pause after 10 seconds and eventually kill the transfer. Removing the "-" from the sharename fixes it.