#!/bin/sh
# Copyright (C) 2007, 2015, 2018  Etersoft
# Copyright (C) 2007  Pavel Vainerman <pv@etersoft.ru>
# Copyright (C) 2015  Vitaly Lipatov <lav@etersoft.ru>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

export LANG=C

DESCR="eterremove version 0.3 (c) Etersoft 2015, 2018"

# realpath workaround
if ! which realpath 2>/dev/null >/dev/null ; then
realpath()
{
	readlink -f "$@"
}
fi

print_message()
{
	local DESC="$1"
	shift
	echo "$DESC in $(basename $0): $@"
}

# Print error message and stop the program
fatal()
{
	print_message Error "$@" >&2
	exit 1
}

assert_var()
{
	local i re

	for i in $@ ; do
		re=$(eval echo \$$i)
		[ -n "$re" ] || fatal "assert: $i is not exists"
	done
}


check_params()
{
        [ -n "$DAYSAGO$SIZELIMIT" ] || fatal "Check --help or man page for correct set days ago or size"
        [ "$DAYSAGO" != "0" ] || fatal "Zero days ago is the day. It is not supported"
        [ "$SIZELIMIT" != "0" ] || fatal "Zero file size is not supported"
        [ -d "$REMOVEDIR" ] || fatal "Working directory does not exists"

        if [ -n "$NOTEST" ] ; then
                CMD_RMRF="rm -vrf"
                CMD_RMF="rm -vf"
                CMD_RMDIRP="rmdir -p --ignore-fail-on-non-empty"
        else
                CMD_RMRF="ls -ld"
                CMD_RMF="ls -ld"
                CMD_RMDIRP="ls -ld"

                echo "It is a test run (no files will be deleted)"
        fi

        return 0
}

# TODO: правильное название
remove_short_paths()
{
        local path
        # Note! do not use local for prev, it sets in piped while
        prev=""

        sort -r | while read path; do
                # skip if prev path starts with the path string
                echo "$prev" | grep -q "^$path" && continue
                echo "$path"
                prev="$path"
        done
}

remove_broken_links()
{
        assert_var REMOVEDIR
        local link

        [ -z "$NOTEST" ] && echo "Broken links to remove below:"
        find "$REMOVEDIR" -depth -type l ! -name "$EXCLUDEDIR" -print | \
                while read link ; do
                        readlink -e "$link" >/dev/null && continue
                        $CMD_RMF "$link"
                done
}

remove_empty_dirs()
{
        assert_var REMOVEDIR DEPTH
        # minutes for empty dir
        local MPAUSE="$1"

        [ -z "$NOTEST" ] && echo "Empty directories to remove below:"

        # clean empty dirs
        #find "$REMOVEDIR" -depth $DEPTHMAX -type d -cmin +$MPAUSE ! -name "$EXCLUDEDIR" -print0 | xargs -0 -- $CMD_RMDIRP

        # clean empty dirs. we use remove_short_paths due rmdir -p will remove all dirs from sequence
        local path
        find "$REMOVEDIR" -depth $DEPTHMAX -type d -cmin +$MPAUSE -empty ! -name "$EXCLUDEDIR" -print | \
                remove_short_paths | while read path ; do
                    # TODO: add error policy
                    $CMD_RMDIRP "$path" && [ -n "$NOTEST" ] && echo "removed '$path'"
                done

        remove_broken_links

}

remove_old_files()
{
        assert_var REMOVEDIR DEPTH

        [ -n "$DAYSAGO" ] || return 0

        [ -z "$NOTEST" ] && echo "Files older than $DAYSAGO below:"

        find "$REMOVEDIR" -depth -mindepth $DEPTH $DEPTHMAX \
                ! -type d -mtime +"$DAYSAGO" ! -name "$EXCLUDEDIR" -print0 | xargs -0 -- $CMD_RMF
}

remove_old_size_files()
{
        assert_var REMOVEDIR DEPTH

        [ -n "$SIZELIMIT" ] || return 0

        [ -z "$NOTEST" ] && echo "Oldest files which overflow capacity in $SIZELIMIT Gb:"

        local SIZEBYTES=$(($SIZELIMIT*1024*1024*1024))
        local TOTAL=0
        # print all files sorted by date. skip first files which summary size not more limit
        find "$REMOVEDIR" -depth -mindepth $DEPTH $DEPTHMAX \
                -type f ! -name "$EXCLUDEDIR" -printf "%T+ %s %p\n" | sort | \
                while read date size path ; do
                        TOTAL=$(($TOTAL+$size))
                        #echo "$date $size $path $TOTAL $SIZEBYTES"
                        [ "$TOTAL" -lt "$SIZEBYTES" ] && continue
                        $CMD_RMF "$path"
                done
}




remove_old_dirs()
{
        assert_var REMOVEDIR DEPTH

        [ -n "$DAYSAGO" ] || return 0

        [ -z "$NOTEST" ] && echo "Directories older than $DAYSAGO below:"

        # TODO: check status (deferred until correct test)
        find "$REMOVEDIR" -depth -mindepth $DEPTH $DEPTHMAX \
                -type d -mtime +"$DAYSAGO" ! -name "$EXCLUDEDIR" -print0 | xargs -0 -- $CMD_RMRF
}


COMMAND=$1
shift

DEPTH=1
if [ "$1" = "-depth" ] || [ "$1" = "--depth" ] ; then
	shift
	DEPTH=$(($1))
	shift
fi

DEPTHMAX=
if [ "$1" = "--depthmax" ] ; then
	shift
	DEPTHMAX="-maxdepth $(($1))"
	shift
fi

EXCLUDEDIR=
if [ "$1" = "--exclude" ] ; then
	shift
	EXCLUDEDIR="$1"
	shift
fi


DAYSAGO=
if [ "$1" = "--days" ] ; then
	shift
	DAYSAGO=$(($1))
	shift
fi

SIZELIMIT=
if [ "$1" = "--size" ] ; then
	shift
	SIZELIMIT=$(($1))
	shift
fi

NOTEST=
if [ "$1" = "--notest" ] ; then
	shift
	NOTEST=1
fi

FORCE=
if [ "$1" = "--force" ] ; then
	shift
	FORCE=1
fi

SUBCOMMAND="$1"
[ -n "$1" ] && shift

case "$COMMAND $SUBCOMMAND" in
	"remove dirs")
		# from
		REMOVEDIR=$(realpath -e "$1") || exit
		check_params
		[ -n "$SIZELIMIT" ] && fatal "--size are not realized yet (need calc du -b -s for every dir)"
		remove_old_dirs
		remove_empty_dirs 0
	;;
	"remove files")
		# from
		REMOVEDIR=$(realpath -e "$1") || exit
		check_params
		remove_old_files
		remove_old_size_files
		remove_empty_dirs 0
	;;
	"remove empty")
		# from
		REMOVEDIR=$(realpath -e "$1") || exit
		# TODO:
		check_params
		[ -n "$SIZELIMIT" ] && fatal "--size are not allowed for remove empty"
		remove_empty_dirs 0
	;;
	"-h "|"--help ")
		echo $DESCR
		echo "Run with $0 command [options] spec path"

		echo
		echo "Remove obsoleted dirs:"
		echo "	$ eterremove remove [options] dirs /path/to/remove"

		echo
		echo "Remove obsoleted files and empty dirs:"
		echo "	$ eterremove remove [options] files /path/to/remove"

		echo
		echo "Remove empty dirs:"
		echo "	$ eterremove remove [options] empty /path/to/remove"

		echo
		echo "Make dir date list less dense:"
		echo "	$ eterremove rarefy [options]"
		echo
		echo "Options:"
		echo "	--depth N          - set work subdirs level (1 by default)"
		echo "	--depthmax N       - set max subdirs level (no range by default)"
		echo "	--exclude name     - exclude dir 'name' from removing"
		echo "	--days N           - remove dirs/files older than N days"
		echo "	--size N           - remove oldest files to fit all in N gigabytes"
		echo "	--notest           - real removing"
		echo "(use options only in that order)"
		#echo "	--force                         force"

		echo
		echo "Examples:"
		echo "	$ eterremove remove --depth 2 --days 30 files /path/to/remove"
		echo "	$ eterremove remove --exclude current --days 10 dirs /path/to/remove"
	;;
	*)
		echo "$DESCR" >&2
		echo "Run with -h or --help for help" >&2
		exit 1
	;;
esac

