#!/bin/bash

#/**
#         checkrepo
#@file    checkrepo
#@brief   Maintain repository information in a JSON file.
#@warning This script uses "jq" command.
#@version 1.1.0 - Initial version.
#@author  Ramón M. Gómez - ETSII Univ. Sevilla
#@date    2017-09-27
#*/ ##


# Global constants definition.
PROG=$(basename "$(realpath "$0")")
OPENGNSYS=/opt/opengnsys
IMAGESDIR=$OPENGNSYS/images
INFOFILE=$OPENGNSYS/etc/repoinfo.json


# Auxiliar functions.

# Metafunction to check if JSON result exists.
function jq() {
    local OUTPUT
    OUTPUT=$($JQ "$@") || return $?
    [[ "$OUTPUT" = "null" ]] && return 1
    echo "$OUTPUT"
}

# Create/edit JSON file about installed ogLive clients.
function addToJson() {
    # Parameters and variables.
    local IMAGENAME="$1" IMAGETYPE="$2" DATA="$3" JSON i j n m OUNAME OUIND IMGIND
    local CLONATOR COMPRESSOR FSTYPE DATASIZE CLIENT
    IFS=":" read -r CLONATOR COMPRESSOR FSTYPE DATASIZE CLIENT <<<"$DATA"
    # Check if image is restricted to an OU (subdir).
    if [[ $IMAGENAME =~ / ]]; then
        OUNAME="${IMAGENAME%/*}"
        IMAGENAME="${IMAGENAME##*/}"
    fi
    # Data size must be numeric (in KB).
    [[ $DATASIZE =~ ^[0-9]*$ ]] || DATASIZE=0
    # JSON-formatted new entry.
    JSON=$(cat << EOT | jq .
{
  "name":"$IMAGENAME",
  "type":"${IMAGETYPE,,}",
  "clientname":"$CLIENT",
  "clonator":"${CLONATOR,,}",
  "compressor":"${COMPRESSOR,,}",
  "filesystem":"${FSTYPE^^}",
  "datasize":$[ DATASIZE * 1024]
}
EOT
    )
    # Check JSON file consistency.
    if [ "$(jq -c keys $INFOFILE 2>/dev/null)" == '["directory","images","ous"]' ]; then
        # Common image.
        if [ -z "$OUNAME" ]; then
            # Check if the image is defined into JSON file.
            n=$(jq ".images | length" $INFOFILE)
            for ((i=0; i<n; i++)); do
                [ "$(jq ".check=$JSON | .check.name==.images[$i].name" $INFOFILE)" == "true" ] && IMGIND=$i
            done
            # Check if it needs to update or insert data.
            if [ -n "$IMGIND" ]; then
                # Update if image data changes and info file exists.
                [ -n "$3" -a "$(jq ".check=$JSON | .check==.images[$IMGIND]" $INFOFILE)" == "false" ] && jq ".images[$IMGIND]=$JSON" $INFOFILE | sponge $INFOFILE
            else
                # Append a new entry.
                jq ".images |= (. + [$JSON])" $INFOFILE | sponge $INFOFILE
            fi
        else    # OU image.
            # Append a new OU entry if it does not exist.
            if [ -z "$(jq -r ".ous[].subdir" $INFOFILE | grep "^$OUNAME$")" ]; then
                JSON=$(cat << EOT | jq .
{ 
  "subdir": "$OUNAME",
  "images": [ $JSON ]
}
EOT
                )
                jq ".ous |= (. + [$JSON])" $INFOFILE | sponge $INFOFILE
            else
                # Check if the image is defined in some OU.
                m=$(jq ".ous | length" $INFOFILE)
                for ((j=0; j<m; j++)); do
                    n=$(jq ".ous[$j].images | length" $INFOFILE)
                    for ((i=0; i<n; i++)); do
                        [ "$(jq ".check=$JSON | .check.name==.ous[$j].images[$i].name" $INFOFILE)" == "true" ] && OUIND=$j && IMGIND=$i
                    done
                done
                # Check if it needs to update or insert data.
                if [ -n "$IMGIND" ]; then
                    # Update if image data changes and info file exists.
                    [ $# -gt 2 -a "$(jq ".check=$JSON | .check==.ous[$OUIND].images[$IMGIND]" $INFOFILE)" == "false" ] && jq ".ous[$OUIND].images[$IMGIND]=$JSON" $INFOFILE | sponge $INFOFILE
                else
                    # Append a new entry.
                    jq ".ous[$OUIND].images |= (. + [$JSON])" $INFOFILE | sponge $INFOFILE
                fi
            fi
        fi
    else
        # Create new JSON file.
        if [ -z "$OUNAME" ]; then
            cat << EOT | jq . > $INFOFILE
{"directory":"$IMAGESDIR","images":[${IMAGENAME:+$JSON}],"ous":[]}
EOT
        else
            cat << EOT | jq . > $INFOFILE
{"directory":"$IMAGESDIR","images":[],"ous":[{"subdir":"$OUNAME","images":[$JSON]}]}
EOT
        fi
    fi
}

# Show an error message.
function raiseError() {
    case "$1" in
        usage)
            echo "$PROG: Usage error: Type \"$PROG help\"" >&2
            exit 1 ;;
        notfound)
            echo "$PROG: Resource not found: $2" >&2
            exit 2 ;;
        access)
            echo "$PROG: Access error: $2" >&2
            exit 3 ;;
        *)
            echo "$PROG: Unknown error" >&2
            exit 1 ;;
    esac
}

# Command functions.

# Show help message.
function help() {
    cat << EOT
$PROG: maintain the repository information.
Usage: $PROG
EOT
}

# Check for file-based images to update the repository configuration file.
function checkfiles() {
    local IMAGES IMG INFO DATA

    # File-stored images.
    IMAGES=$(find $IMAGESDIR -maxdepth 2 -type f \( -name "*.img" -o -name "*.dsk" \) -print)
    for IMG in $IMAGES; do
        # Skip locked images.
        [ -e "$IMG.lock" ] && continue
        # Retrieve image creation data and delete temporary file.
        INFO="$IMG.info"
        [ -e "$INFO" -a "$INFO" -ot "$IMG" ] && rm -f "$INFO" && echo "Warning: Deleted outdated file $INFO"
        DATA=""
        [ -r "$INFO" ] && DATA=$(cat "$INFO")
        # Add data to configuration file (name, type and data) and remove image info file.
        IMG=${IMG#$IMAGESDIR/}
        addToJson "${IMG%.*}" "${IMG##*.}" "$DATA" && rm -f "$INFO"
    done
}

# Check for directory-based images to update the repository configuration file.
function checkdirs() {
    local IMAGES IMG INFO DATA

    # Directory-based images.
    IMAGES=$(find $IMAGESDIR -maxdepth 3 -type f -name ogimg.info -print)
    for INFO in $IMAGES; do
        IMG="$(dirname "${INFO#$IMAGESDIR/}")"
        # Skip repository root directory and locked images.
        [ "$IMG" == "$IMAGESDIR" -o -e "$IMG.lock" ] && continue
        DATA=$(awk -F= '$1=="# fstype" {fs=$2} $1=="# sizedata" {sz=$2} END {printf "rsync::%s:%s:",fs,sz}' "$INFO")
        # Add data to configuration file (name, type and data).
        addToJson "$IMG" "dir" "$DATA"
    done
}

# Check if images are removed to update the repository configuration file.
function checkremoved() {
    local IMG TYPE OU i j n m
    [ ! -w "$INFOFILE" ] && raiseError access "$INFOFILE"

    # Check if global images are defined into JSON file.
    n=$(jq ".images | length" $INFOFILE)
    for ((i=0; i<n; i++)); do
        # Image name and type.
        IMG="$(jq -r ".images[$i].name" $INFOFILE)"
        TYPE="$(jq -r ".images[$i].type" $INFOFILE)"
        [ "$TYPE" != "dir" ] && IMG="$IMG.$TYPE"
        # Delete entry if image does not exist and it's not locked.
        [ ! -e "$IMAGESDIR/$IMG" -a ! -e "$IMAGESDIR/$IMG.lock" ] && jq "del(.images[$i])" $INFOFILE | sponge $INFOFILE
    done
    # Check if OU images are defined into JSON file.
    m=$(jq ".ous | length" $INFOFILE)
    for ((j=0; j<m; j++)); do
        # OU subdir.
        OU="$(jq -r ".ous[$j].subdir" $INFOFILE)"
        # Delete OU's entries if its subdir does not exist.
        if [ ! -e "$IMAGESDIR/$OU" ]; then
            jq "del(.ous[$j])" $INFOFILE | sponge $INFOFILE
        else
            n=$(jq ".images | length" $INFOFILE)
            for ((i=0; i<n; i++)); do
                # Image name and type.
                IMG="$(jq -r ".ous[$j].images[$i].name" $INFOFILE)"
                TYPE="$(jq -r ".ous[$j].images[$i].type" $INFOFILE)"
                [ "$TYPE" != "dir" ] && IMG="$IMG.$TYPE"
                # Delete entry if image does not exist and it's not locked.
                [ ! -e "$IMAGESDIR/$OU/$IMG" -a ! -e "$IMAGESDIR/$OU/$IMG.lock" ] && jq "del(.ous[$j].images[$i])" $INFOFILE | sponge $INFOFILE
            done
        fi
    done
}


# Main progrram.

# Check dependencies.
JQ=$(which jq 2>/dev/null) || raiseError notfound "Need to install \"jq\"."
which sponge &>/dev/null || raiseError notfound "Need to install \"moreutils\"."
[ ! -w "$(dirname "$INFOFILE")" ] && raiseError access "$INFOFILE"
[ -e "$INFOFILE" ] || addToJson

checkfiles
checkdirs
checkremoved

