#!/bin/bash

#   ------------------------------------------------------------------
#
#   Shell program to download a clouds map for xplanet from a random
#   mirror, replacing the previously downloaded clouds map only after
#   checking its correctness.
#
#   Copyright 2004-2007, Thorsten Bonow <toto@withouthat.org>.
#
#   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.
#
#   Description:
#
#   Usage:
#
#       xplanet-update-cloudmap.sh [ -h | --help ] | [ -v | --verbose ]
#
#   Options:
#
#       -h, --help    Display this help message and exit.
#       -v, --verbose Output informative messages about what the script
#                     is doing.
#
#   External programs:
#
#       sed      - stream editor
#       basename - strip directory and suffix from filenames
#       pgrep    - look up or signal processes based on name and other
#                  attributes
#       getopt   - parse command options (enhanced)
#       date     - print or set the system date and time
#       find     - search for files in a directory hierarchy
#       wget     - the non-interactive network downloader.
#       cp       - copy files and directories
#       file     - determine file type
#       grep     - print lines matching a pattern
#       stat     - display file or file system status
#       mktemp   - make temporary filename (unique)
#
#   Revision History:
#
#   2004-03-20      File created by new_script ver. 2.1.0
#                   See ChangeLog for additional changes
#
#   CVS: $Id: xplanet-update-cloudmap.sh,v 1.5 2007-02-24 22:30:15 toto Exp $
#
#   ------------------------------------------------------------------

##### Preamble #####

#   ------------------------------------------------------------------
#   The list of cloud map mirrors. Add new ones here.
#   ------------------------------------------------------------------

    declare -a CLOUDMAP_MIRRORS=(
	"ftp://mirror.pacific.net.au/pub2/xplanet/clouds_2048.jpg"
        "http://xplanet.fortha.org/clouds_2048.jpg"
        "http://www.ruwenzori.net/earth/clouds_2048.jpg"
        "http://xplanet.dyndns.org/clouds/clouds_2048.jpg"
        "http://userpage.fu-berlin.de/~jml/clouds_2048.jpg"
        "http://rcswww.urz.tu-dresden.de/~es179238/clouds_2048.jpg"
        "http://home.megapass.co.kr/~jhkim1101/cloud_data/clouds_2048.jpg"
        "http://user.chol.com/~winxplanet/cloud_data/clouds_2048.jpg"
        "http://home.megapass.co.kr/~gitto88/cloud_data/clouds_2048.jpg"
        "http://myhome.hanafos.com/~hyoungkee/cloud_data/clouds_2048.jpg"
        "http://giga.forfun.net/clouds_2048.jpg"
        "http://php.nctu.edu.tw/~ijliao/clouds_2048.jpg"
        "http://www.wizabit.eclipse.co.uk/xplanet/files/mirror/clouds_2048.jpg"
        "http://www.wizabit.eclipse.co.uk/xplanet/files/local/clouds_2048.jpg"
        "ftp://ftp.iastate.edu/pub/xplanet/clouds_2048.jpg"
        "http://enekoalonso.com/projects/xplanet/clouds_2048.php"
        "http://xplanet.nerp.net/clouds_2048.php"
	)

#   ------------------------------------------------------------------
#   Constants
#   ------------------------------------------------------------------

    PATH=/sbin:/usr/sbin:/bin:/usr/bin

    declare -r PROGNAME=$(basename $0)
    declare -r VERSION="0.9.6"

    declare -r CLOUDMAP_DIR=/usr/local/share/xplanet/images
    declare -r CLOUDMAP_FILE=clouds_2048.jpg

    # how often to download the image? Defaults to 3h
    declare -r MAX_DOWNLOAD_FREQUENCY_MINUTES=180

    # how often to try to download
    declare -r MAX_TRY=10

    # output of "file" when checking a jpeg image
    declare -r JPEG_STRING="JPEG image data"

    # minimum size in bytes for a valid cloud map
    declare -i CLOUDMAP_MIN_SIZE=210000

    # wget timeout
    declare -i WGET_TIMEOUT=60


##### Functions #####

#   ------------------------------------------------------------------
#   Functions
#   ------------------------------------------------------------------


function clean_up() {

#   ------------------------------------------------------------------
#   Function to remove temporary files and other housekeeping
#       No arguments
#   ------------------------------------------------------------------

    rm -f ${TEMP_FILE1}
    return

}   # end of clean_up


function message() {

#   ------------------------------------------------------------------
#   Function to echo a message.
#       Arguments:
#           1 (required) MESSAGE_TEXT
#   ------------------------------------------------------------------
    
    # Fatal error if required argument is missing

    if [ "$1" = "" ]; then 
        error_exit "message: missing argument 1"
    fi

    if [ "$verbose" = "true" ]; then
 	printf "%s\n" "$1"
    fi

    return   
             
}   # end of message


function error_exit() {

#   ------------------------------------------------------------------
#   Function for exit due to fatal program error
#       Arguments:
#           1 (optional) string containing descriptive error message
#   ------------------------------------------------------------------


    printf "%s\n" "${PROGNAME}: ${1:-"Unknown Error"}" >&2
    clean_up
    exit 1

}   # end of error_exit


function graceful_exit() {

#   ------------------------------------------------------------------
#   Function called for a graceful exit
#       No arguments
#   ------------------------------------------------------------------

    clean_up
    exit

}   # end of graceful_exit


function signal_exit() {

#   ------------------------------------------------------------------
#   Function to handle termination signals
#       Arguments:
#           1 (optional) signal_spec
#   ------------------------------------------------------------------

    case $1 in
        INT) 
            message "$PROGNAME: Program aborted by user" >&2
            clean_up
            exit
            ;;
        TERM)
            message "$PROGNAME: Program terminated" >&2
            clean_up
            exit
            ;;
        *)
            error_exit "$PROGNAME: Terminating on unknown signal"
            ;;
            
    esac

}   # end of signal_exit


function make_temp_files() {

#   ------------------------------------------------------------------
#   Function to create temporary files
#       No arguments
#   ------------------------------------------------------------------

    # Temp file for this script, using paranoid method of creation to
    # insure that file name is not predictable.  This is for security
    # to avoid "tmp race" attacks.  If more files are needed, create
    # using the same form.

    TEMP_FILE1=$(mktemp -q "${CLOUDMAP_DIR}/${PROGNAME}.$$.XXXXXX")
    if [ "$TEMP_FILE1" = "" ]; then
        error_exit "cannot create temp file!"
    fi

}   # end of make_temp_files


function usage() {

#   ------------------------------------------------------------------
#   Function to display usage message (does not exit)
#       No arguments
#   ------------------------------------------------------------------

    printf "%s\n" "Usage: ${PROGNAME} [-h | --help] | [ -v | --verbose ]"

}   # end of usage


function helptext() {

#   ------------------------------------------------------------------
#   Function to display help message for program
#       No arguments
#   ------------------------------------------------------------------

    cat <<EOF

    ${PROGNAME} ${VERSION}

    This is a program to download a clouds map for xplanet from a
    random mirror, replacing the previously downloaded clouds map only
    after checking its correctness.

    $(usage)

    Options:

        -h, --help    Display this help message and exit.
        -v, --verbose Output informative messages about what the script
                      is doing.
EOF

}   # end of helptext


function check_for_other_instances() {

#   ------------------------------------------------------------------
#   Function to terminate this script in case of other running
#   instances
#       No arguments
#   ------------------------------------------------------------------

    if [ $(pgrep -f -c $PROGNAME) -gt 1 ]; then
	message "Another instance of this script is running, exiting."
	graceful_exit
    fi

    return

}   # end of check_for_other_instances



function initialisation() {

#   ------------------------------------------------------------------
#   Function to initialise the script
#       No arguments
#   ------------------------------------------------------------------

    while [ -n "${CLOUDMAP_MIRRORS[${NO_OF_CLOUDMAP_MIRRORS}]}" ]; do
	let "NO_OF_CLOUDMAP_MIRRORS += 1"
    done
    
    # initialisation of random number generator
    RANDOM=$(date +$s)

    return

}   # end of initialisation


function shut_down() {

#   ------------------------------------------------------------------
#   Function to shut down the script before exiting
#       No arguments
#   ------------------------------------------------------------------

    return

}   # end of shutdown


function check_permissions() {

#   ------------------------------------------------------------------
#   Function to check for necessary file permissions
#       No arguments
#   ------------------------------------------------------------------

    if [ ! -w "$CLOUDMAP_DIR" ]; then
	error_exit "Cloud map directory '$CLOUDMAP_DIR' is not writable."
    elif [ -e "$CLOUDMAP_DIR/$CLOUDMAP_FILE" ]; then
	if [ ! -r "$CLOUDMAP_DIR/$CLOUDMAP_FILE" ]; then
	    error_exit "Cloud map file '$CLOUDMAP_FILE' is not a regular file."
	elif [ -d "$CLOUDMAP_DIR/$CLOUDMAP_FILE" ]; then
	    error_exit "Cloud map file '$CLOUDMAP_FILE' is a directory."	
	elif [ ! -w "$CLOUDMAP_DIR/$CLOUDMAP_FILE" ]; then
	    error_exit "Cloud map file '$CLOUDMAP_FILE' is not writable."	
	else
	    OLD_CLOUDMAP_FILE_EXISTS="true"
	fi
    else
	OLD_CLOUDMAP_FILE_EXISTS="false" 
    fi

    return

}   # end of check_permissions


function random_number() {

#   ------------------------------------------------------------------
#   Function to calculate a random number between 0 and the
#   NO_OF_CLOUDMAP_MIRRORS-1
#       No arguments
#   ------------------------------------------------------------------

    # random number generator has been initialised in initialisation
    # function
    rnd_number=$RANDOM
    let "rnd_number %= $NO_OF_CLOUDMAP_MIRRORS"

    return

}   # end of random_number


function check_update() {

#   ------------------------------------------------------------------
#   Function to check if cloud map is old enough to be updated
#       No arguments
#   ------------------------------------------------------------------

    if [ "$(find $CLOUDMAP_DIR -mmin -$MAX_DOWNLOAD_FREQUENCY_MINUTES -name $CLOUDMAP_FILE)" == "$CLOUDMAP_DIR/$CLOUDMAP_FILE" ]; then
	message "Cloud map is already up to date."
	graceful_exit
    fi

    return

}   # end of check_update


function update_cloudmap() {

#   ------------------------------------------------------------------
#   Function to update the cloudmap
#       No arguments
#   ------------------------------------------------------------------

    local -i i

    message "Trying mirrors..."
    for ((i=0; i <= $MAX_TRY ; i++)); do
	random_number;
	wget -q --timeout=$WGET_TIMEOUT ${CLOUDMAP_MIRRORS[$rnd_number]} \
	    --output-document="${TEMP_FILE1}";
	if [ $? == 0 ] ; then
	    message "Download successful, validating..."
	    validate_cloudmap && break
	fi
    done

    return

}   # end of update_cloudmap


function validate_cloudmap() {

#   ------------------------------------------------------------------
#   Function to validate the correctness of the cloudmap
#       No arguments
#   ------------------------------------------------------------------

    local -i return_value=1

    # "Missing file" HTML document is downloaded if there is no cloud
    # map
    file "$TEMP_FILE1" | grep -q "$JPEG_STRING"
    if [ $? == 0 ] ; then
	if [ $(stat -c%s "$TEMP_FILE1") -gt $CLOUDMAP_MIN_SIZE ]; then
	    return_value=0	
	else
	    message "Cloud map to small."
	fi
    else
	message "Downloaded cloud map is not a valid JPEG."
    fi

    if [ $return_value == 0 ]; then
	cp "$TEMP_FILE1" "$CLOUDMAP_DIR/$CLOUDMAP_FILE" && \
	    message "Cloud map updated."
    else
	message "Cloud map not updated."
    fi

    return $return_value

}   # end of validate_cloudmap


#   ------------------------------------------------------------------
#   Program starts here
#   ------------------------------------------------------------------

    # verbosity; false by default since this script is normally run by
    # cron
    declare verbose="false"


##### Command Line Processing #####

# Note that we use `"$@"' to let each command-line parameter expand to
# a separate word. The quotes around `$@' are essential! We need
# GETOPT_TEMP as the `eval set --' would nuke the return value of
# getopt.
GETOPT_TEMP=$(getopt -o +hv --long help,verbose -n "$PROGNAME" -- "$@")

if [ $? != 0 ] ; then
    error_exit "Error parsing command line. Terminating..."
fi

# Note the quotes around `$GETOPT_TEMP': they are essential!
eval set -- "$GETOPT_TEMP"

# no error checking necessary; sanity of command line and required
# arguments has been checked by getopt program
while true ; do
    case $1 in
        -h|--help)
            helptext ;
            graceful_exit
            ;;
        -v|--verbose)
	    verbose="true" ;
            shift
            ;;
        --)
            shift ;
            break
            ;;
        *)
            # should be impossible to reach: getopt should have caught
            # an error
            error_exit "This should not have happened; unknown option '$1'. Terminating..."
            ;;
    esac
done
unset GETOPT_TEMP

# processing remaining arguments for the client
if [ $# -ne 0  ]; then
    usage
    clean_up
    exit 1
fi


##### Initialization And Setup #####

# only one instance should run at a time
check_for_other_instances

# check if update is necessary
check_update

# check file and directory permissions
check_permissions

# Trap TERM, HUP, and INT signals and properly exit

trap "signal_exit TERM" TERM HUP
trap "signal_exit INT"  INT

## Create temporary file(s)
#
make_temp_files


##### Main Logic #####

# is there a valid cloud map to be updated?
declare OLD_CLOUDMAP_FILE_EXISTS="false"
# No of cloud map mirrors
declare -i NO_OF_CLOUDMAP_MIRRORS=0

# the random number specifying the mirror to use
declare -i rnd_number=0

initialisation

update_cloudmap

shut_down

graceful_exit

# end of xplanet-update-cloudmap.sh

# Local Variables:
# mode: outline-minor
# outline-regexp: "\\(function\\)\\|\\(##### \\)"
# outline-heading-end-regexp: "\\(() {\n\\)\\|\\( #####\n\\)"
# fill-column: 70
# End:
