CAN_BIND=${CAN_BIND:-1}

# Add a file or a directory to the list of paths to backup
# 
# Note: this helper could be used in backup hook or in backup script inside an
# app package
#
# Details: ynh_backup writes SRC and the relative DEST into a CSV file. And it
# creates the parent destination directory 
# 
# If DEST is ended by a slash it complete this path with the basename of SRC.
#
# usage: ynh_backup src [dest [is_big [arg]]]
# | arg: src - file or directory to bind or symlink or copy. it shouldn't be in
# the backup dir. 
# | arg: dest - destination file or directory inside the
# backup dir
# | arg: is_big - 1 to indicate data are big (mail, video, image ...)
# | arg: arg - Deprecated arg
#
# example:
# # Wordpress app context
#
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf"
# # => This line will be added into CSV file
# # "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf"
#
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "conf/nginx.conf"
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/nginx.conf"
#
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "conf/"
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf"
# 
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "conf"
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf"
#
# #Deprecated usages (maintained for retro-compatibility)
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "${backup_dir}/conf/nginx.conf"
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/nginx.conf"
#
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "/conf/"
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf"
#
ynh_backup() {
    # TODO find a way to avoid injection by file strange naming !
    local SRC_PATH="$1"
    local DEST_PATH="${2:-}"
    local IS_BIG="${3:-0}"
    BACKUP_CORE_ONLY=${BACKUP_CORE_ONLY:-0}

    # If backing up core only (used by ynh_backup_before_upgrade),
    # don't backup big data items
    if [ "$IS_BIG" == "1" ] && [ "$BACKUP_CORE_ONLY" == "1" ] ; then
      echo "$SRC_PATH will not be saved, because backup_core_only is set." >&2
      return 0
    fi
    
    # ==============================================================================
    # Format correctly source and destination paths
    # ==============================================================================
    # Be sure the source path is not empty
    [[ -e "${SRC_PATH}" ]] || {
        echo "Source path '${SRC_PATH}' does not exist" >&2
        return 1
    }

    # Transform the source path as an absolute path
    # If it's a dir remove the ending /
    SRC_PATH=$(realpath "$SRC_PATH")

    # If there is no destination path, initialize it with the source path
    # relative to "/".
    # eg: SRC_PATH=/etc/yunohost -> DEST_PATH=etc/yunohost
    if [[ -z "$DEST_PATH" ]]; then

        DEST_PATH="${SRC_PATH#/}"

    else
        if [[ "${DEST_PATH:0:1}" == "/" ]]; then

            # If the destination path is an absolute path, transform it as a path
            # relative to the current working directory ($YNH_CWD)
            #
            # If it's an app backup script that run this helper, YNH_CWD is equal to
            # $YNH_BACKUP_DIR/apps/APP_INSTANCE_NAME/backup/
            #
            # If it's a system part backup script, YNH_CWD is equal to $YNH_BACKUP_DIR
            DEST_PATH="${DEST_PATH#$YNH_CWD/}"

            # Case where $2 is an absolute dir but doesn't begin with $YNH_CWD
            [[ "${DEST_PATH:0:1}" == "/" ]] \
                && DEST_PATH="${DEST_PATH#/}"
        fi

        # Complete DEST_PATH if ended by a /
        [[ "${DEST_PATH: -1}" == "/" ]] \
            &&  DEST_PATH="${DEST_PATH}/$(basename $SRC_PATH)"
    fi

    # Check if DEST_PATH already exists in tmp archive
    [[ ! -e "${DEST_PATH}" ]] || {
        echo "Destination path '${DEST_PATH}' already exist" >&2
        return 1
    }

    # Add the relative current working directory to the destination path
    local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR}"
    REL_DIR="${REL_DIR%/}/"
    DEST_PATH="${REL_DIR}${DEST_PATH}"
    DEST_PATH="${DEST_PATH#/}"
    # ==============================================================================

    # ==============================================================================
    # Write file to backup into backup_list
    # ==============================================================================
    local SRC=$(echo "${SRC_PATH}" | sed -r 's/"/\"\"/g')
    local DEST=$(echo "${DEST_PATH}" | sed -r 's/"/\"\"/g')
    echo "\"${SRC}\",\"${DEST}\"" >> "${YNH_BACKUP_CSV}"

    # ==============================================================================

    # Create the parent dir of the destination path
    # It's for retro compatibility, some script consider ynh_backup creates this dir
    mkdir -p $(dirname "$YNH_BACKUP_DIR/${DEST_PATH}")
}

# Restore all files linked to the restore hook or to the restore app script
#
# usage: ynh_restore
#
ynh_restore () {
    # Deduce the relative path of $YNH_CWD
    local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}"
    REL_DIR="${REL_DIR%/}/"

    # For each destination path begining by $REL_DIR
    cat ${YNH_BACKUP_CSV} | tr -d $'\r' | grep -ohP "^\".*\",\"$REL_DIR.*\"$" | \
    while read line; do
        local ORIGIN_PATH=$(echo "$line" | grep -ohP "^\"\K.*(?=\",\".*\"$)")
        local ARCHIVE_PATH=$(echo "$line" | grep -ohP "^\".*\",\"$REL_DIR\K.*(?=\"$)")
        ynh_restore_file "$ARCHIVE_PATH" "$ORIGIN_PATH"
    done
}

# Return the path in the archive where has been stocked the origin path
#
# [internal]
#
# usage: _get_archive_path ORIGIN_PATH
_get_archive_path () {
    # For security reasons we use csv python library to read the CSV
    sudo python -c "
import sys
import csv
with open(sys.argv[1], 'r') as backup_file:
    backup_csv = csv.DictReader(backup_file, fieldnames=['source', 'dest'])
    for row in backup_csv:
        if row['source']==sys.argv[2].strip('\"'):
            print row['dest']
            sys.exit(0)
    raise Exception('Original path for %s not found' % sys.argv[2])
    "  "${YNH_BACKUP_CSV}" "$1"
    return $?
}

# Restore a file or a directory 
#
# Use the registered path in backup_list by ynh_backup to restore the file at
# the good place.
#
# usage: ynh_restore_file ORIGIN_PATH [ DEST_PATH ]
# | arg: ORIGIN_PATH - Path where was located the file or the directory before
# to be backuped or relative path to $YNH_CWD where it is located in the backup archive
# | arg: DEST_PATH   - Path where restore the file or the dir, if unspecified,
# the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in
# the archive, the destination will be searched into backup.csv
#
# If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in 
# /home/yunohost.conf/backup/. Otherwise, the existing file is removed.
#
# examples:
# ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf"
# # if apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf exists, restore it into
# # /etc/nginx/conf.d/$domain.d/$app.conf
# # if no, search a correspondance in the csv (eg: conf/nginx.conf) and restore it into
# # /etc/nginx/conf.d/$domain.d/$app.conf
#
# # DON'T GIVE THE ARCHIVE PATH:
# ynh_restore_file "conf/nginx.conf"
#
ynh_restore_file () {
    local ORIGIN_PATH="/${1#/}"
    local ARCHIVE_PATH="$YNH_CWD${ORIGIN_PATH}"
    # Default value for DEST_PATH = /$ORIGIN_PATH
    local DEST_PATH="${2:-$ORIGIN_PATH}"

    # If ARCHIVE_PATH doesn't exist, search for a corresponding path in CSV
    if [ ! -d "$ARCHIVE_PATH" ] && [ ! -f "$ARCHIVE_PATH" ] && [ ! -L "$ARCHIVE_PATH" ]; then
        ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")"
    fi

    # Move the old directory if it already exists
    if [[ -e "${DEST_PATH}" ]]
    then
        # Check if the file/dir size is less than 500 Mo
        if [[ $(du -sb ${DEST_PATH} | cut -d"/" -f1) -le "500000000" ]]
        then
            local backup_file="/home/yunohost.conf/backup/${DEST_PATH}.backup.$(date '+%Y%m%d.%H%M%S')"
            mkdir -p "$(dirname "$backup_file")"
            mv "${DEST_PATH}" "$backup_file"	# Move the current file or directory
        else
            ynh_secure_remove ${DEST_PATH}
        fi
    fi

    # Restore ORIGIN_PATH into DEST_PATH
    mkdir -p $(dirname "$DEST_PATH")

    # Do a copy if it's just a mounting point
    if mountpoint -q $YNH_BACKUP_DIR; then
        if [[ -d "${ARCHIVE_PATH}" ]]; then
            ARCHIVE_PATH="${ARCHIVE_PATH}/."
            mkdir -p "$DEST_PATH"
        fi
        cp -a "$ARCHIVE_PATH" "${DEST_PATH}"
    # Do a move if YNH_BACKUP_DIR is already a copy
    else
        mv "$ARCHIVE_PATH" "${DEST_PATH}"
    fi
}

# Deprecated helper since it's a dangerous one!
#
# [internal]
#
ynh_bind_or_cp() {
    local AS_ROOT=${3:-0}
    local NO_ROOT=0
    [[ "${AS_ROOT}" = "1" ]] || NO_ROOT=1
    echo "This helper is deprecated, you should use ynh_backup instead" >&2
    ynh_backup "$1" "$2" 1
}

# Create a directory under /tmp
#
# [internal]
#
# Deprecated helper
#
# usage: ynh_mkdir_tmp
# | ret: the created directory path
ynh_mkdir_tmp() {
    echo "The helper ynh_mkdir_tmp is deprecated." >&2
    echo "You should use 'mktemp -d' instead and manage permissions \
properly with chmod/chown." >&2
    local TMP_DIR=$(mktemp -d)

    # Give rights to other users could be a security risk.
    # But for retrocompatibility we need it. (This helpers is deprecated)
    chmod 755 $TMP_DIR
    echo $TMP_DIR
}

# Calculate and store a file checksum into the app settings
#
# $app should be defined when calling this helper
#
# usage: ynh_store_file_checksum file
# | arg: file - The file on which the checksum will performed, then stored.
ynh_store_file_checksum () {
	local checksum_setting_name=checksum_${1//[\/ ]/_}	# Replace all '/' and ' ' by '_'
	ynh_app_setting_set $app $checksum_setting_name $(sudo md5sum "$1" | cut -d' ' -f1)
}

# Verify the checksum and backup the file if it's different
# This helper is primarily meant to allow to easily backup personalised/manually 
# modified config files.
#
# $app should be defined when calling this helper
#
# usage: ynh_backup_if_checksum_is_different file
# | arg: file - The file on which the checksum test will be perfomed.
#
# | ret: Return the name a the backup file, or nothing
ynh_backup_if_checksum_is_different () {
	local file=$1
	local checksum_setting_name=checksum_${file//[\/ ]/_}	# Replace all '/' and ' ' by '_'
	local checksum_value=$(ynh_app_setting_get $app $checksum_setting_name)
	if [ -n "$checksum_value" ]
	then	# Proceed only if a value was stored into the app settings
		if ! echo "$checksum_value $file" | sudo md5sum -c --status
		then	# If the checksum is now different
			local backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
			sudo mkdir -p "$(dirname "$backup_file")"
			sudo cp -a "$file" "$backup_file"	# Backup the current file
			echo "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" >&2
			echo "$backup_file"	# Return the name of the backup file
		fi
	fi
}

# Remove a file or a directory securely
#
# usage: ynh_secure_remove path_to_remove
# | arg: path_to_remove - File or directory to remove
ynh_secure_remove () {
	local path_to_remove=$1
	local forbidden_path=" \
	/var/www \
	/home/yunohost.app"

	if [[ "$forbidden_path" =~ "$path_to_remove" \
		# Match all paths or subpaths in $forbidden_path
		|| "$path_to_remove" =~ ^/[[:alnum:]]+$ \
		# Match all first level paths from / (Like /var, /root, etc...)
		|| "${path_to_remove:${#path_to_remove}-1}" = "/" ]]
		# Match if the path finishes by /. Because it seems there is an empty variable
	then
		echo "Avoid deleting $path_to_remove." >&2
	else
		if [ -e "$path_to_remove" ]
		then
			sudo rm -R "$path_to_remove"
		else
			echo "$path_to_remove wasn't deleted because it doesn't exist." >&2
		fi
	fi
}