#!/bin/sh

dest_url=/mirror

dest_dir=/srv/public/mirror
config_dir=/etc/alterator/mirror/config
repository_dir=/etc/apt/repositories
mirror_dir=/etc/apt/mirrors
schedule_file=/etc/cron.d/alterator-mirror
mirror_cmd=/usr/sbin/alterator-mirror

alterator_api_version=1

url_re='^\(ftp://\|http://\|rsync://\|file:/\)[a-zA-Z0-9._/:@-]\+$'
rsync_url_re='^rsync://[a-zA-Z0-9._/-:@]\+$'

. alterator-sh-functions
. avahi-sh-functions
. shell-quote
. shell-config

### low level

repository_awk()
{
    alterator-dump-desktop \
        -v lang="$in_language" \
        -v out="Filename;X-Path;X-Has-Noarch;X-Mirror;Name" \
        -v def="noname;/notfound;no;yes" \
	"$@"
}

mirror_awk()
{
    alterator-dump-desktop \
        -v lang="$in_language" \
        -v out="Filename;X-Repositories;Name;Comment" \
        -v def="noname;all" \
	"$@"
}

config_get()
{
    local v="$(shell_config_get "$config_dir/$1" "$2")"
    string_quote_remove "$v"
}

config_set()
{
    local f="$config_dir/$1"
    [  -s "$f" ] || echo ". $config_dir/.common" >"$f"

    shell_config_set "$f" "$2" "\"$3\""
}

tr_size()
{
    sed -e "s/[[:space:]]*M/ `_ "Mb"`/" \
	-e "s/[[:space:]]*K/ `_ "Kb"`/" \
	-e "s/[[:space:]]*G/ `_ "Gb"`/" \
	-e "s/[[:space:]]*T/ `_ "Tb"`/"
}


### high level
read_arch()
{
    local arch="$(config_get "$1" ARCH)"
    echo "${arch%% noarch}"
}

read_local()
{
    [ "$(config_get "$1" ALTERATOR_LOCAL)" = "yes" ]
}

read_publish()
{
    [ "$(config_get "$1" ALTERATOR_PUBLISH)" = "yes" ]
}

write_publish()
{
    local name="$1";shift
    local status="$1";shift

    if test_bool "$status"; then
	local mirror="$(config_get "$name" ALTERATOR_MIRROR)"
	local custom_url="$(config_get "$name" ALTERATOR_CUSTOM_URL)"

	[ "$mirror" = "custom" ] || custom_url=

	if read_local "$name"; then
	    mirror=custom
	    custom_url="$dest_url"
	fi

	config_set "$name" ALTERATOR_PUBLISH yes
	publish_service "alterator-mirror-$name" "System updates at %h ($name)" "_apt._tcp" 0 \
		"r=$name" \
		"m=$mirror" \
		"u=$custom_url" \
		"a=$(read_arch "$name")"
    else
	config_set "$name" ALTERATOR_PUBLISH no
	unpublish_service "alterator-mirror-$name"
    fi
}

list_repository()
{
    repository_awk $repository_dir/*.desktop|
	while read name path noarch mirror description;do
	    [ "$mirror" = "yes" ] || continue

	    name="${name##*/}"
	    name="${name%.desktop}"

	    if [ ! -s "$config_dir/$name" ];then
		write_table_item \
		    name "$name" \
		    description "$description" \
		    mirror "" \
		    arch "" \
		    local_status "off" \
		    local_du "" \
		    publish "off"
		continue
	    fi

	    local r_mirror="$(config_get "$name" ALTERATOR_MIRROR)"

	    local r_path=
	    if [ "$r_mirror" = "custom" ];then
		r_path="$(config_get "$name" ALTERATOR_CUSTOM_URL)"
	    elif [ -n "$r_mirror" ];then
		r_path="$(alterator-dump-desktop -v lang="$in_language" -v out=Name "$mirror_dir/$r_mirror.desktop")"
	    fi

	    local r_arch="$(read_arch "$name")"

	    local r_local_du="($(run_localized du -sh "$dest_dir/$path" 2>/dev/null| cut -f1| tr_size))"
	    local r_local_status=
	    if read_local "$name"; then
		[ "$r_local_du" != '()' ] || r_local_du="(0 `_ "Kb"`)"
		r_local_status="on"
	    else
		[ "$r_local_du" != "()" ] || r_local_du=""
		r_local_status="off"
	    fi

	    r_publish=
	    if read_publish "$name"; then
		r_publish="on"
	    else
		r_publish="off"
	    fi

	    write_table_item \
		name "$name" \
		description "$description" \
		mirror "$r_path" \
		arch "$r_arch" \
		local_status "$r_local_status" \
		local_du "$r_local_du" \
		publish "$r_publish"
	done
}

list_mirror()
{
    local repo="$1";shift
    local IFS="	"

    mirror_awk $mirror_dir/*.desktop|
	while read id repolist name comment; do

	    repolist=";$repolist;"
	    [ "$repolist" = ";all;" -o -z "${repolist##*;$repo;*}" ] || continue

	    id="${id##*/}"
	    id="${id%.desktop}"

	    printf '%s\t%s (%s)\n' "$id" "$name" "$comment"
	done | write_enum

    write_enum_item "custom" "`_ "Custom url"`"
}

list_arch()
{
    write_enum_item "i586"
    write_enum_item "x86_64"
    local r_file="$repository_dir/$in_name.desktop"
    local r_arepo="$(shell_config_get "$r_file" X-Has-Arepo)"
    local r_arm="$(shell_config_get "$r_file" X-Has-ARM)"
    if [ "$r_arepo" = "yes" ]; then
	    write_enum_item "x86_64-i586"
    fi
    if [ "$r_arm" = "yes" ]; then
	write_enum_item "arm"
    fi
}


read_storage()
{
    local real_dest_dir="$(readlink -e "$dest_dir")"
    run_localized df -lhP |
	sed 1d |
	sort -r -k 6,6 |
	while read fs total use avail percent mpoint; do
	    [ -z "${real_dest_dir##$mpoint*}" ] || continue
	    write_string_param "storage_avail" "$(echo "$avail"|tr_size)"
	    break
	done
}

get_rsync_url()
{
    if [ "$1" = "custom" ];then
	echo "$2"
    else
	shell_config_get "$mirror_dir/$1.desktop" X-RSYNC-URI
    fi
}

read_schedule()
{
    if [ -s "$schedule_file" ] ;then
	while read min hour monthday month weekday rest;do
	    [ -n "${min%\#*}" ] || continue

	    write_string_param "time" "$hour:$min"
	    if [ "$monthday" = "*" -a "$month" = "*" -a "$weekday" = "*" ]; then
	        write_string_param "period" "daily"
	    elif [ "$monthday" = "*" -a "$month" = "*" -a "$weekday" != "*" ];then
	        write_string_param "period" "weekly"
	        write_string_param "weekday" "$weekday"
	    elif [ "$monthday" != "*" -a "$month" = "*" -a "$weekday" = "*" ]; then
	        write_string_param "period" "monthly"
	        write_string_param "monthday" "$monthday"
	    else
	        write_string_param "period" "daily"
	    fi
	    return
	done <"$schedule_file"
    else
	write_string_param "period" "off"
	write_string_param "monthday" "1"
	write_string_param "weekday" "1"
	write_string_param "time" "02:00"
    fi
}

write_schedule()
{
    if [ "$in_period" == "off" ];then
	rm -f -- "$schedule_file"
	return
    fi

    if [ "$in_period" = "weekly" -a -z "$in_weekday" ]; then
	write_error "`_ "Day of week should be selected"`"
	return
    fi

    if [ "$in_period" = "monthly" -a -z "$in_monthday" ];then
	write_error "`_ "Day of month should be defined"`"
	return
    fi

    local hour="${in_time%%:*}"
    local min="${in_time#*:}"; min="${min%%:*}"

    local new_schedule_file="$(mktemp "$schedule_file.XXXXXXXXXX")"
    if [ -z "$new_schedule_file" ]; then
	write_error "`_ "Unable to create temp file"`"
	return
    fi

    printf "#autogenerated by alterator-mirror\n" >"$new_schedule_file"
    case "$in_period" in
	daily)
	    printf '%s %s * * * root %s\n' "$min" "$hour" "$mirror_cmd"
	    ;;
	weekly)
	    printf '%s %s * * %s root %s\n' "$min" "$hour" "$in_weekday" "$mirror_cmd"
	    ;;
	monthly)
	    printf '%s %s %s * * root %s\n' "$min" "$hour" "$in_monthday" "$mirror_cmd"
	    ;;
    esac >>"$new_schedule_file"
    mv -f "$new_schedule_file" "$schedule_file"
}

list_weekday()
{
    write_enum_item "1" "`_ "monday"`"
    write_enum_item "2" "`_ "tuesday"`"
    write_enum_item "3" "`_ "wednesday"`"
    write_enum_item "4" "`_ "thursday"`"
    write_enum_item "5" "`_ "friday"`"
    write_enum_item "6" "`_ "saturday"`"
    write_enum_item "0" "`_ "sunday"`"
}


## initial tuning
[ "$(config_get ".common" DESTROOT)" = "$dest_dir" ] ||
    config_set ".common" DESTROOT "$dest_dir"

mkdir -p -- "$dest_dir"

on_message()
{
	case "$in_action" in
		type)
			write_type_item	name	hostname
			write_type_item	arch	keyword-list
			write_type_item	weekday	integer
			write_type_item	time	crontime
			;;
		list)
			case "$in__objects" in
			    avail_repository)
				list_repository;;
			    avail_arch)
				list_arch;;
			    avail_weekday)
				list_weekday;;
			    avail_mirror)
				[ -n "$in_name" ] && list_mirror "$in_name" ;;
			esac
			;;
		validate_mirror)
			[ "$in_mirror" = "custom" ] ||
			    grep -qs 'X-RSYNC-URI=' "$mirror_dir/$in_mirror.desktop" ||
			    write_error "Not a rsync mirror"
			;;
		read)
		    case "$in__objects" in
			/)
			    read_schedule
			    read_storage
			    ;;
			repository)
			    [ -n "$in_name" ] || return

			    repository_awk $repository_dir/$in_name.desktop|
			        (read name path noarch mirror description;
				    write_string_param description "$description")

			    write_string_param	arch		"$(config_get "$in_name" ARCH|tr ' ' ';')"
			    write_string_param	mirror		"$(config_get "$in_name" ALTERATOR_MIRROR)"
			    write_string_param	custom_url	"$(config_get "$in_name" ALTERATOR_CUSTOM_URL)"
			    write_bool_param	local		"$(config_get "$in_name" ALTERATOR_LOCAL)"
			    write_bool_param	publish		"$(config_get "$in_name" ALTERATOR_PUBLISH)"
			    ;;
		    esac
		    ;;
		write)
		    case "$in__objects" in
			/)
			    write_schedule
			    ;;
			repository)
			    if [ -n "$in_clear" -a -n "$in_name" ];then
				repository_awk "$repository_dir/$in_name.desktop"|
				(read name path noarch description;
				    [ -n "$path" ] && rm -rf "$dest_dir/$path")
			    elif [ -n "$in_commit" ];then
				local r_file="$repository_dir/$in_name.desktop"
				local r_path="$(shell_config_get "$r_file" X-Path)"
				local r_noarch="$(shell_config_get "$r_file" X-Has-Noarch)"

				#check custom_url
				if [ "$in_mirror" = "custom" ] ;then
				    if [ -z "$in_custom_url" ];then 
					write_error "`_ "Custom url should be defined"`"
					return
				    elif ! echo "$in_custom_url"|grep -qs "$url_re";then
					write_error "`_ "Invalid or unsupported protocol. Should be http,ftp or rsync"`"
					return
				    elif [ -n "$in_local" ] && ! echo "$in_custom_url"|grep -qs "$rsync_url_re";then
					write_error "`_ "Can mirror only from sources with rsync procotol"`"
					return
				    fi
				fi

				config_set "$in_name" LIST "$r_path"
				[ -n "$in_arch" -a "$r_noarch" = "yes" ] && in_arch="${in_arch};noarch"
				config_set "$in_name" ARCH "$(echo $in_arch|tr ';' ' ')"

				config_set "$in_name" ALTERATOR_MIRROR "$in_mirror"
				config_set "$in_name" ALTERATOR_CUSTOM_URL "$in_custom_url"

				local rsync_url="$(get_rsync_url "$in_mirror" "$in_custom_url")"
				if [ -n "$rsync_url" ] && test_bool "$in_local"; then
				    config_set "$in_name" ALTERATOR_LOCAL yes
				    config_set "$in_name" SRCROOT "$rsync_url"
				else
				    config_set "$in_name" ALTERATOR_LOCAL no
				    config_set "$in_name" SRCROOT ""
				fi

				write_publish "$in_name" "$in_publish"
			    fi
			    ;;
		    esac
		    ;;
	esac
}

message_loop
