#!/bin/sh
#set -x
#IS_DEBUG=1

IS_ROOT=/tmp/is-root
DL_DIR=${IS_ROOT}/tmp/dl
LISTS_DIR_O=/tmp/opkg-lists
LISTS_DIR=${IS_ROOT}${LISTS_DIR_O}
OPKG_CONF_DIR=${IS_ROOT}/etc/opkg
APP_LIST_FILE=/etc/istore/app.list
BACKUP_CONFIG_FILE=/etc/config/istore
FEEDS_SERVER=https://istore.linkease.com/repo
ARCH=`jsonfilter -i /etc/.app_store.id -e '$.arch'`

# for istore self upgrade
ISTORE_PKG=luci-app-store
ISTORE_INDEX=https://istore.linkease.com/repo/all/store/Packages.gz

action=${1}
shift

is_init() {
    mkdir -p ${DL_DIR} ${LISTS_DIR} ${IS_ROOT}/etc ${IS_ROOT}/var

    cat /etc/opkg.conf | grep -v lists_dir | grep -v check_signature > ${IS_ROOT}/etc/opkg.conf

    cp ${IS_ROOT}/etc/opkg.conf ${IS_ROOT}/etc/opkg_o.conf

    echo >> ${IS_ROOT}/etc/opkg.conf
    echo "lists_dir ext ${LISTS_DIR}" >> ${IS_ROOT}/etc/opkg.conf
    echo >> ${IS_ROOT}/etc/opkg_o.conf
    echo "lists_dir ext ${LISTS_DIR_O}" >> ${IS_ROOT}/etc/opkg_o.conf

    cp -au /etc/opkg ${IS_ROOT}/etc/
    [ -e ${IS_ROOT}/var/lock ] || ln -s /var/lock ${IS_ROOT}/var/lock
}

opkg_wrap() {
    OPKG_CONF_DIR=${OPKG_CONF_DIR} opkg -f ${IS_ROOT}/etc/opkg.conf "$@"
}

fcurl() {
    curl --fail --show-error "$@"
}

update() {
    if [ -z "${ARCH}" ]; then
        echo "Get architecture failed" >&2
        echo "/etc/.app_store.id:" >&2
        cat /etc/.app_store.id >&2
        return 1
    fi

    fcurl -o ${OPKG_CONF_DIR}/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
      fcurl -o ${OPKG_CONF_DIR}/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
      fcurl -o ${OPKG_CONF_DIR}/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" || \
      return 1

    opkg -f ${IS_ROOT}/etc/opkg_o.conf --offline-root ${IS_ROOT} update

    return 0
}

update_if_outdate() {
    local idle_t=$((`date '+%s'` - `date -r ${IS_ROOT}/.last_force_ts '+%s' 2>/dev/null || echo '0'`))
    [ $idle_t -gt ${1:-120} ] || return 2
    update || return 1
    touch ${IS_ROOT}/.last_force_ts
    return 0
}

check_self_upgrade() {
    local newest=`curl --connect-timeout 2 --max-time 5 -s ${ISTORE_INDEX} | gunzip | grep -FA10 "Package: ${ISTORE_PKG}" | grep -Fm1 'Version: ' | sed 's/^Version: //'`
    local current=`grep -Fm1 'Version: ' /usr/lib/opkg/info/${ISTORE_PKG}.control | sed 's/^Version: //'`
    if [ "v$newest" = "v" -o "v$current" = "v" ]; then
        echo "Check version failed!" >&2
        exit 255
    fi
    if [ "$newest" != "$current" ]; then
        echo "$newest"
    fi
    return 0
}

do_self_upgrade() {
    check_mtime || return 1
    if opkg_wrap info ${ISTORE_PKG} | grep -qF not-installed ; then
        true
    else
        update_if_outdate
        local code=$?
        [ "$code" = 1 ] && return 1
        if [ "$code" = 2 ] || ! opkg_wrap info ${ISTORE_PKG} | grep -qF not-installed; then
            echo "already the latest version!" >&2
            return 1
        fi
    fi
    opkg_wrap upgrade ${ISTORE_PKG}
}

check_mtime() {
    find ${OPKG_CONF_DIR}/arch.conf -mtime -1 2>/dev/null | grep -q .  || update
}

wrapped_in_update() {
    check_mtime || return 1
    opkg_wrap "$@" && return 0
    update_if_outdate || return 1
    opkg_wrap "$@"
}

new_upgrade() {
    check_mtime || return 1
    local metapkg=`echo "$@" | sed 's/ /\n/g' | grep -F app-meta-`
    if [ -z "$metapkg" ] || opkg_wrap info $metapkg | grep -qF not-installed ; then
        true
    else
        update_if_outdate
    fi
    wrapped_in_update upgrade "$@"
}

opkg_list_installed_packages() {
    target=$1
    case $target in
        "preinstalled")
            OPKG_INFO_DIR="/rom/usr/lib/opkg/info"
        ;;
        "userinstalled")
            OPKG_INFO_DIR="/overlay/upper/usr/lib/opkg/info"
        ;;
        "allinstalled")
            OPKG_INFO_DIR="/usr/lib/opkg/info"
        ;;
        *)
            echo "invalid target"
            exit
        ;;
    esac

    awk -v D="cd $OPKG_INFO_DIR &&" 'BEGIN {
        C=D"ls *.list"
        S="sort -n"
        while(C|getline>0) {
            P=substr(F=$1,1,length($1)-5)
            J=D"du -sk $(cat "F")"
            s=0
            while(J|getline>0) s+=$1
            close(J)
            t+=s
            print s" "P|S
        }
        close(S)
    }'
}

ipk_build() {
    PKG_NAME_TEMP=$1
    IPK_OUTPUT_DIR=$2

    UCI_BAK_DIR="/etc/istore/uci-defaults_bak/"
    UCI_DEF_DIR="etc/uci-defaults"
    OPKG_INFO_DIR="/usr/lib/opkg/info/"

    [ -n "${PKG_NAME_TEMP}" ] || exit 1
    #get real pkg name in opkg
    PKG_NAME_TEMP=`cat ${IS_ROOT}/all_installed_package.list | sort -u | grep "^${PKG_NAME_TEMP}" | head -n 1`
    [ -n "${PKG_NAME_TEMP}" ] || exit 1

    PKG_NAME=`cat ${OPKG_INFO_DIR}${PKG_NAME_TEMP}.control | grep "^Package: " |  cut -d ' ' -f2`
    PKG_VER=`cat ${OPKG_INFO_DIR}${PKG_NAME}.control | grep "^Version: " |  cut -d ' ' -f2`
    PKG_ARCH=`cat ${OPKG_INFO_DIR}${PKG_NAME}.control | grep "^Architecture: " |  cut -d ' ' -f2`
    IPK_FILE_NAME="${PKG_NAME}_${PKG_VER}_${PKG_ARCH}"

    rm -rf ${IS_ROOT}/${IPK_FILE_NAME}
    mkdir -p ${IS_ROOT}/${IPK_FILE_NAME}

    #(1)make CONTROL dir; (2)copy control file to dir
    cd ${IS_ROOT}/${IPK_FILE_NAME}
    mkdir -p CONTROL
    for control_file in `ls  ${OPKG_INFO_DIR}${PKG_NAME}.* | grep -v ".list$"`; do
        file=${control_file##*/}
        suffix=${file##*.}
        cp ${control_file} CONTROL/${suffix}
    done

    #(1)make DATA depend dir; (2)copy uci-defaults_bak file to dir; (3)copy other file to dir
    for pkgfile in `cat ${OPKG_INFO_DIR}${PKG_NAME}.list | cut -b 2-`; do
        file=${pkgfile##*/}
        path=${pkgfile%/*}
        mkdir -p ${path}
        if [ `echo "${path}" | grep "^${UCI_DEF_DIR}"` ]; then
            cp "${UCI_BAK_DIR}${file}" "${pkgfile}"
        else
            cp "/${pkgfile}" "${pkgfile}"
        fi
    done

    #call ipkg-build script to build ipk
    ipkg-build ${IS_ROOT}/${IPK_FILE_NAME} ${IPK_OUTPUT_DIR}
    echo "${IPK_FILE_NAME}.ipk" >> ${IPK_OUTPUT_DIR}/appdepipk.list

    [ -n "${IS_DEBUG}" ] || rm -rf ${IS_ROOT}/${IPK_FILE_NAME}
}

# if arg is NULL, use light backup, otherwise use local backup
backup() {
    [ -n "$1" ] && BACKUP_PATH=$1

    #1.add all istore self data to sysupgrade config file,
    #sysupgrade will backup/restore it auto when flash new firmware
    echo "/etc/.app_store.id" > /lib/upgrade/keep.d/luci-app-store
    cat /usr/lib/opkg/info/luci-app-store.list >> /lib/upgrade/keep.d/luci-app-store
    echo "/etc/rc.d/S45istore" >> /lib/upgrade/keep.d/luci-app-store
    echo "/etc/istore/uci-defaults_bak" >> /lib/upgrade/keep.d/luci-app-store
    echo "${APP_LIST_FILE}" >> /lib/upgrade/keep.d/luci-app-store
    echo "${BACKUP_CONFIG_FILE}" >> /lib/upgrade/keep.d/luci-app-store

    #2.backup all installed package lists to config file
    update  #if want not depend this, need record package list by istore to file when is-opkg update

    #get feed list
    opkg_conf_list="meta all arch"
    feed_name_list=""
    for conf in ${opkg_conf_list}; do
        feed_name=`cat ${OPKG_CONF_DIR}/${conf}.conf | cut -d ' ' -f2`
        feed_name_list="${feed_name} ${feed_name_list}"
    done
    [ -n "${IS_DEBUG}" ] && echo ${feed_name_list}

    istore_package_list=""
    #get istore package list
    for feed_name in ${feed_name_list}; do
        package_list=`cat ${LISTS_DIR}/${feed_name} | gunzip | grep "^Package: " | cut -d ' ' -f2`
        istore_package_list="${package_list} ${istore_package_list}"
    done
    [ -n "${IS_DEBUG}" ] && echo ${istore_package_list}

    #write istore package list to file
    echo ${istore_package_list} | tr " " "\n" | sort -n | uniq > ${IS_ROOT}/istore_support_package.list

    #write all installed package list to file
    opkg_list_installed_packages "allinstalled" 2>/dev/null | cut -d ' ' -f2 | sort -u > ${IS_ROOT}/all_installed_package.list

    #write user installed package list to file
    opkg_list_installed_packages "userinstalled" 2>/dev/null | cut -d ' ' -f2 | sort -u > ${IS_ROOT}/user_installed_package.list

    #write system pre installed package list to file
    opkg_list_installed_packages "preinstalled" 2>/dev/null | cut -d ' ' -f2 | sort -u > ${IS_ROOT}/pre_installed_package.list

    #write installed package list by istore feed to file
    cat ${IS_ROOT}/istore_support_package.list ${IS_ROOT}/user_installed_package.list | \
        sort -n | uniq -d > ${IS_ROOT}/istore_installed_package.list

    #if no input backup path, only back app.list
    mkdir -p /etc/istore
    cp ${IS_ROOT}/istore_installed_package.list ${APP_LIST_FILE}
    echo "backup installed package list to ${APP_LIST_FILE}"

    if [ ! -n "${BACKUP_PATH}" ]; then
        echo "backup success"
        exit 0
    fi

    #write installed packages and depends list by istore feed to file by depend sequence
    appdep_list=""
    temp_list=`cat ${IS_ROOT}/istore_installed_package.list | sed 's/^/\t/'`
    while [ -n "${temp_list}" ]
    do
        #get real pkg name
        for PKG_NAME_TEMP in ${temp_list}; do
            REAL_PKG_NAME=`cat ${IS_ROOT}/all_installed_package.list | sort -u | grep "^${PKG_NAME_TEMP}" | head -n 1`
            if [ "${REAL_PKG_NAME}" != "${PKG_NAME_TEMP}" ]; then
                temp_list=`echo "${temp_list}" | sed 's/^\t'"${PKG_NAME_TEMP}"'$/\t'"${REAL_PKG_NAME}"'/'`
            fi
        done

        appdep_list=`echo -e "${temp_list}\n${appdep_list}"`
        [ -n "${IS_DEBUG}" ] && echo -e "temp_list:\n""${temp_list}"
        [ -n "${IS_DEBUG}" ] && echo -e "appdep_list:\n""${appdep_list}"

        temp_list=`echo "${temp_list}" | xargs opkg depends | grep -v "depends on:" | grep -v " (>= " | grep -v " (= " | sort -u`
    done

    appdep_list_all=`echo "${appdep_list}" | cut -f2 | grep -v "^$" | awk '!seen[$0]++'`
    [ -n "${IS_DEBUG}" ] && echo -e "appdep_list_all:\n""${appdep_list_all}"
    echo "${appdep_list_all}" > ${IS_ROOT}/appdep.list


    #3.rebuild all istore installed package to ipk and backup to userdata partation
    if [ ! -d "${BACKUP_PATH}" ];then
        echo "invalid backup path, can not backup ipk"
        exit 1
    fi

    # 4. create dir
    date=$(date +%Y-%m%d-%H%M)
    if [ ! -d "$BACKUP_PATH/backup_istore_$date" ];then
        mkdir $BACKUP_PATH/backup_istore_$date
    fi
    cp ${IS_ROOT}/istore_installed_package.list $BACKUP_PATH/backup_istore_$date/app.list
    cp ${IS_ROOT}/appdep.list $BACKUP_PATH/backup_istore_$date/appdep.list

    #only backup non pre installed ipk
    cp ${IS_ROOT}/appdep.list ${IS_ROOT}/appdep_strip.list
    for pre_installed_pkg in `cat ${IS_ROOT}/appdep.list ${IS_ROOT}/pre_installed_package.list | sort -n | uniq -d`; do
        sed -i '/^'"$pre_installed_pkg"'$/d' ${IS_ROOT}/appdep_strip.list
    done

    rm -f $BACKUP_PATH/backup_istore_$date/appdepipk.list
    echo "build ipk"
    for pkg_name in `cat ${IS_ROOT}/appdep_strip.list`; do
        ipk_build ${pkg_name} $BACKUP_PATH/backup_istore_$date
    done

    # 5. create tar.gz file,and remove fir
    cd $BACKUP_PATH
    echo "write backup file to $BACKUP_PATH/backup_istore_$date.backup.tar.gz"
    tar -czf $BACKUP_PATH/backup_istore_$date.backup.tar.gz backup_istore_$date
    rm -rf $BACKUP_PATH/backup_istore_$date
    echo "backup success"
}

# if arg is NULL, use light backup, otherwise use local backup
restore() {
    if [ -n "$1" ]; then
        BACKUP_PATH_FILE=$1
    else
        echo "install package by ${APP_LIST_FILE}"
        update
        for app in `cat ${APP_LIST_FILE}`; do
            #skip resotre istore self
            [ "A${app}" == "A""luci-app-store" ] && continue
            opkg_wrap install ${app}
        done
        exit 0
    fi

    if [ ! -f "${BACKUP_PATH_FILE}" ];then
        echo "invalid backup file, can not restore ipk"
        exit 1
    fi

    #1. Unzip file to dir
    BACKUP_PATH_FILE_NAME=${BACKUP_PATH_FILE##*/}
    BACKUP_PATH=/tmp/${BACKUP_PATH_FILE_NAME%.backup.tar.gz*}
    if [ -d "$BACKUP_PATH" ];then
        rm -rf $BACKUP_PATH
    fi
    mkdir -p $BACKUP_PATH
    echo "unpack input file..."
    # fix tar path error
    tar -zxf ${BACKUP_PATH_FILE} -C /tmp/

    echo "check file"
    if [ ! -f "${BACKUP_PATH}/appdep.list" ];then
        echo "no available appdep.list, can not restore ipk"
        exit 1
    fi
    echo "check success"

    #2. install ipk by backup path
    echo "restore begin"
    for app in `cat ${BACKUP_PATH}/appdepipk.list`; do
        opkg_wrap install ${BACKUP_PATH}/${app}
    done

    #3. rm dir
    rm -rf ${BACKUP_PATH}
    echo "restore success"
}

get_support_backup_features() {
    echo "light_backup"
    #istore custom img mean support local_backup
    if [ -f /etc/istore_img_flag ];then
        echo "local_backup"
    fi
}

get_backup_app_list_file_path() {
    echo "${APP_LIST_FILE}"
}

get_backup_app_list() {
    if [ ! -f "${APP_LIST_FILE}" ];then
        echo "no app.list, can not get backup app list"
        exit 1
    fi
    cat ${APP_LIST_FILE}
}

get_available_backup_file_list() {
    if [ -n "$1" ]; then
        for backup_file in `ls $1/*.backup.tar.gz`; do
            filename=${backup_file##*/}
            echo "${filename}"
        done
    else
        echo "input backup path is null"
        exit 1
    fi
}

usage() {
    echo "usage: is-opkg sub-command [arguments...]"
    echo "where sub-command is one of:"
    echo "      update                          Update list of available packages"
    echo "      upgrade <pkgs>                  Upgrade package(s)"
    echo "      install <pkgs>                  Install package(s)"
    echo "      remove <pkgs|regexp>            Remove package(s)"
    echo "      info [pkg|regexp]               Display all info for <pkg>"
    echo "      list-upgradable                 List installed and upgradable packages"
    echo "      check_self_upgrade              Check iStore upgrade"
    echo "      do_self_upgrade                 Upgrade iStore"
    echo "      backup [dir]                    Backup all installed package(s) to [directory]"
    echo "      restore [dir]                   Restore package(s) by [directory]"
    echo "      get_support_backup_features     get device support backup features"
    echo "      get_backup_app_list_file_path   get light backup app list file path"
    echo "      get_backup_app_list             get light backup app list"
    echo "      get_available_backup_file_list  get local available backup file list"
    echo "      opkg                            sys opkg wrap"
}

is_init >/dev/null 2>&1

case $action in
    "update")
        update
    ;;
    "install")
        wrapped_in_update install "$@"
    ;;
    "upgrade")
        new_upgrade "$@"
    ;;
    "remove")
        opkg_wrap --autoremove --force-removal-of-dependent-packages remove "$@"
    ;;
    "info")
        opkg_wrap info "$@"
    ;;
    "list-upgradable")
        opkg_wrap list-upgradable
    ;;
    "check_self_upgrade")
        check_self_upgrade
    ;;
    "do_self_upgrade")
        do_self_upgrade
    ;;
    "get_support_backup_features")
        get_support_backup_features
    ;;
    "backup")
        backup "$@"
    ;;
    "restore")
        restore "$@"
    ;;
    "get_backup_app_list_file_path")
        get_backup_app_list_file_path
    ;;
    "get_backup_app_list")
        get_backup_app_list
    ;;
    "get_available_backup_file_list")
        get_available_backup_file_list "$@"
    ;;
    "opkg")
        opkg_wrap "$@"
    ;;

    *)
        usage
    ;;
esac
