# call:
#	[gap=N #h]
#	mirror SOURCE_RSYNC_URL DESTINATION_DIRECTORY [FILTER...]
# Creates files in ~ directory.
# 2009-04-23
# 2011-09-14 .. min ago -> + .. h ago
# 2011-09-23 same change in another place
# 2011-09-29 default gap - 2 h
# 2011-11-17 default rsync options and path
# 2011-11-22 -f to force mirroring (despite the $gap time has not passed); floting point calculations; -h - help; -n - dry run
# 2011-11-28 printf formats corrections; -29 \n before "MIRRORING"
# 2011-12-12 $p alias for /packages
# 2011-12-13 date after "MIRRORING"
# 2011-12-30 corr. for fractional gaps; typo
# 2011-01-02 check rsync error and recover the old time stamp
# 2012-01-31 --timeout=3600 spotted in Debian's ftpsync; rsync error was checked reversed!
# 2013-11-08 +$optionslocal for rsync to enable adding options to standard $options - if $options are to be changed, the script should redeclare them
# 2014-01-10 reset LC_NUMERIC to "C" - just unsetting it is not enough for printf command
# 2014-01-29 $options: +--chmod=og-w cos openmandriva mirror uses 666/777 permissions
# 2014-05-15 if default destination path is empty use $p / basename $0; remote duplicate slashes, e.g. in /$p (//packages)
# 2015-01-22 e-mail on error, to whoami by default; better rsync return code check
# 2015-02-20 recently mirrored -> recently successfully mirrored ... last attempt
# 2015-02-25 check if .stamp, .filter or .stamp.old files exist (recent mirroring: date of .filter, recent successfull mirroring: date od .stamp)
# 2016-10-13 defoptions variable for use in restoring options when changed in a script
# 2016-11-19 do not run rsync if free space low
# 2017-03-22 add 0 to free space to cover case of non-existent fs
# 2017-04-12 adding 0 not enough, we should handle $NR==0
# 2017-05-30,06-01 main switch
# TODO: file marker: UPDATING; list recent times option; -w option to wait for lock relasing instead of skipping (for regular random mirrors); timeout rsync for redhat archives; when space low, start with deleting
unset LANG
export LC_NUMERIC=C

off=~/MIRROR_OFF
if [ -f $off ]; then echo "Mirroring blocked (`cat $off`). Remove $off to start again."; exit 1; fi

p=/packages	# alias
#maxuse=99 #%	(of the destination fs)
minfree=1024 # MB

count=0; select=0; force=0; echo=""
while [[ "$1" =~ -[fhn] ]]; do
	case "$1" in
		"-h") shift; cat <<EOT
$0 [-h] [-f [NUM]] [-n]
Script for mirroring rsync packages backed by mirror-functions library.
-h		this help
-f [NUM]	mirror even if the gap time set in the script have not passed.
                With NUM converns only package number NUM (conting from 1)
-n		dry run - do not execute rsync commans
EOT
		exit;;
		"-f") force=1; shift; if [[ "$1" =~ ^[0-9] ]]; then select=$1; shift; fi;;
		"-n") shift; echo=echo;;
	esac
done

mirror() {
	local remote=$1
	local mirrordir
	if [ "$2" == "" ]; then
		mirrordir=$p/` basename $0`		# if the destination path not specified
	else
		mirrordir=$2				# otherwise use the specified one
	fi
	mirrordir=`echo "$mirrordir" | sed 's%//%/%g'`	# remove duplicate slashes
	local lock=~/`echo $mirrordir | tr / .`
	local filter="${lock}.filter"
	local stamp="${lock}.stamp"
	printf '\nMIRRORING: %s -> %s   @%s\n' $remote $mirrordir "`date`"
	free=`df -PB1M $mirrordir/ | awk '$0 !~ /^Filesystem/ { print $(NF-2) + 0 } END { if ($NR==0) print 0 }'`
	if [ "$free" -le "$minfree" ]; then printf "THE DESTINATION FS FILLED, %d MB <= %d MB (minfree). EXITING...\n" "$free" "$minfree"; return; fi

	count=$(( $count + 1 ))				# submirrors counter

	# remove lock if no such process
	if [ -f "$lock" ]; then if ! ps hp `cat $lock`; then echo Deleting a stale lock file.; rm -f $lock; fi; fi

	if [ ! -e "$stamp" ]; then touch -d "100000 hours ago" "$stamp"; fi	# just in case, to get a reasonable result from substraction
	if [ ! -e "$filter" ]; then touch -d "100000 hours ago" "$filter"; fi

	# to not to nag to often
	if [ "$gap" == "" ]; then gap=2; fi	# in h

	if [ "$rsync" == "" ]; then rsync="rsync"; fi	# default path
	defoptions="-varSH --partial --delete --delete-after --delete-excluded --timeout=3600 --chmod=og-w"
	if [ "$options" == "" ]; then options="$defoptions"; fi
	if [ "$email" == "" ]; then email="`whoami`"; fi

	# time stamps of filter (recently touched) and stamp (backed off on unsuccessfull attempt)
	tcurr=`date "+%s"`; tstamp=`stat -c "%Y" "$stamp"`; tfilter=`stat -c "%Y" "$filter"`
	if [ "$tstamp" == "" ]; then tstamp=0; fi
	if [ "$tfilter" == "" ]; then tstamp=0; fi
	abovegap=`echo "$tcurr - $tstamp - $gap * 3600" | bc -lq | sed 's/\..*//'`
	min=`echo "($tcurr - $tstamp) / 60" | bc -lq`
	hours=`echo "($tcurr - $tstamp) / 3600" | bc -lq`
	minf=`echo "($tcurr - $tfilter) / 60" | bc -lq`
	hoursf=`echo "($tcurr - $tfilter) / 3600" | bc -lq`
	if [ $abovegap -le 0 -a $force == 0 ]; then
		printf 'Successfully mirrored only %.0f min (%.1f h) ago (last attempt %.1f h ago) which is less than %.1f h, skipping.\n' $min $hours $hoursf $gap
		return
	fi

	if [ $force != 0 ]; then
		if [  $select == 0 -o $select == $count ]; then
			printf 'Forcing another mirroring of this package recently successfully mirrored %.0f min (%.1f h) ago (last attempt %.1f h ago), while gap is %.1f h, continuing...\n' $min $hours $hoursf $gap
		else
			printf 'Skipping.\n'
			return
		fi
	else
		printf 'successfully mirrored %.0f min (%.1f h) ago (last attempt %.1f h ago), which is more than %.1f h, continuing...\n' $min $hours $hoursf $gap
	fi

	mv "$stamp" "$stamp".old
	touch "$stamp"
	if lockfile -3 -r 1 -'!' $lock; then
		echo Lock: skipping this section.
	else
		chmod u+w $lock; echo $$ > $lock; chmod u-w $lock
		echo -n > "$filter"
		while [ "$3" != "" ]; do
			echo "$3" >> "$filter"
			shift
		done
		$echo $rsync --filter ". $filter" $options $optionslocal $remote $mirrordir
		if [ $? == 0 ]; then			# on error e-mail and don't count this attempt
			true
		else
			echo $remote | mail -s "ERROR: `basename $0` $remote" "$email"
			if [ -f "$stamp".old ]; then mv "$stamp".old "$stamp"; fi
		fi
		rm -f $lock
	fi
	echo ''
}
