#!/bin/bash
DEVNULL=''
#INSTALL_DIR нельзя определить в pfs с aufs-n из-за рекурсии
INSTALL_DIR="$(aufs-n --raw '$dname_source' |grep modules |head -n1)" # папка куда качается модуль с ключом -i

SORT_STR='-t = -bk2,2n -bk3,3n -bk4,4n'  # параметры сортировки

# здесь подтянется pfs и можно переопределить из него переменные 
if [ -f $(dirname $0)/pfs ] ;  then
    . $(dirname $0)/pfs
else 
    . $(which pfs) || exit 13 
fi    

####### Ф У Н К Ц И И ########################
#$1 modname
get_requires() {
	local deplist
	deplist=$(pfsinfo -s $1 |grep ^Dependencies: |sed -e 's/^Dependencies://' -e 's/,/ /g') 
	[ -z "$deplist" ] && return
	echo "$deplist" |sed 's/-\{2,\}//g' >> ${WORK_DIR}/requires.lst
	sed -i 's/^[ \t\n]*$//' ${WORK_DIR}/requires.lst
}

# $1 файл (локальный, http, ftp, rsync)
# $2 файл для сохранения 
# вернет имя файла с путем
getfile() {
	local file='/' 
	geturl() {
		if  echo "$1" | egrep -q "^rsync://[a-z0-9./-]*" ; then
			eval rsync -avzz --progress $1 $2 1>&2 $DEVNULL   || rm -f $2
		else
			eval wget -t 2 -O $2  $1 $DEVNULL || rm -f $2
		fi
		
	}
	mkdir -p "$(dirname $2)"
	if echo "$1" | egrep -q "^file://[a-z0-9./-]*" ; then
		file=$(echo $1 |sed 's#^file:/##')
	elif echo "$1" | egrep -q "^[a-z]{3,6}://[a-z0-9./-]*"; then
    	geturl $1 $2
	else
		file="$1"  
	fi
	[ -f "$file" -a "$(realpath "$file")" != "$(realpath "$2")" ] && cp -a "$file" "$2"
	[ -f "$2" ] && echo $2
}

check_net() {
	ping -c2 ya.ru >/dev/null && return 0
	wget ya.ru -O - 2>/dev/null |grep -q DOCTYPE && return 0 
    echo "Вероятно сеть не доступна, прервать? (y/n)"
    read qqq 
    [ "$qqq" == 'y' -o "$qqq" == 'Y' ] &&  exit $LINENO
}

# Сделать реполист для папки $1
# Если указать имя с путем для файла реполиста ($2), то будет создан реполист без подписи, 
# без сжатия и с прописанными путями к каждому файлу. Это нужно для указания папки с модулями в качестве репы.
# Если указан только $1 будет создан реполист со стандартным именем _REPOLIST.gz
# В случае настроенного gpg, также будет создан отдельный файл ключ _REPOLIST.gz.sig
# Каталог с этими файлами можно целиком синкать на сервер с любым протоколом, без правок реполиста.
# $1 папка
# $2 реполист вне репы
mkrepolist() {
	local reponame REPOLIST DATE SIZE VER REV MD5 LOCAL a NAME PACKNAME \
	MODVER MODREV ALIASES PACKALIASES AUTHOR MODAUTHOR
	reponame=$(realpath $1)
	REPOLIST="${reponame}/_REPOLIST"
	if [ $2 ] ; then
	 REPOLIST=$2
	 LOCAL=yes
	fi 
	: > $REPOLIST
	for a in $(find $reponame -maxdepth 2 -type f -name "*.$EXT") ; do
		if [ "$LOCAL" = yes ] ; then
			SUBPATH_a="file:/$a"
		else 
			SUBPATH_a=$(echo $a |sed "s:${reponame}/::")
		fi
		unsquashfs -d /tmp/info "$a" -e ${PFSDIR}/INFO >/dev/null 2>&1
		source /tmp/info/${PFSDIR}/INFO 2>/dev/null
		DATE=$(ls -la $a --time-style=+%F |cut -d " " -f6)
		SIZE=$(du -h $a |cut -f1)
		VER=$MODVER 
		[ -z $VER ] && VER=0
		REV=$MODREV 
		[ -z $REV ] && REV=0
		PACKNAME=$NAME ; [ -z $PACKNAME ] && PACKNAME=$(basename $a)
		PACKALIASES=$ALIAS ; [ -z $PACKALIASES ] && PACKALIASES="$PACKNAME" 
		MODAUTHOR=$AUTHOR ; [ -z $MODAUTHOR ] && MODAUTHOR='unknown'
		MD5=$(md5sum $a |awk '{print $1}')
		echo "$SUBPATH_a VER=$VER REV=$REV DATE=$DATE NAME=$PACKNAME \
		ALIASES=$PACKALIASES AUTHOR=$MODAUTHOR SIZE=$SIZE MD5=$MD5 " >> $REPOLIST
		unset VER REV NAME PACKNAME MODVER MODREV DATE SIZE MD5 ALIASES PACKALIASES AUTHOR MODAUTHOR
		rm -rf /tmp/info 2>/dev/null
	done 
	if [ "$LOCAL" != yes ] ; then
		gzip $REPOLIST 2>/dev/null && REPOLIST=${REPOLIST}.gz
		gpg --detach-sign --output $REPOLIST.sig $REPOLIST 2>/dev/null
	fi
	[ -f $REPOLIST ] && echo $REPOLIST
}

# склеивает списки в форматах txt и txt.gz в один
make_full_repolist() {
	:> ${WORK_DIR}/sfs-full-repolist
	for a in $(find ${WORK_DIR}/lists -type f -name *.list ) ; do
		zcat $a  2>/dev/null || cat $a 
	done |sort |uniq |awk '$1=$1' >> ${WORK_DIR}/sfs-full-repolist
} 

# поиск модуля с самой свежей версией
# $1 шаблон поиска для grep (ищет только отдельное слово)
find_mods() {
	new=$(cat ${WORK_DIR}/sfs-full-repolist | grep -wi "$1" |sort $SORT_STR |head -n1)
if [ "$2" == new ] ; then
	echo "$new" 
	return
elif [ "$2" == all ] ; then
	cat ${WORK_DIR}/sfs-full-repolist | grep -wi "$1" |sort $SORT_STR  | grep -v "$new" | cut -d " " -f1 
fi
}

# поиск по синонимам
# $1 что ищем
# $2 файл с синонимами
find_alias() {
	FIND=$(grep -m1 -w "$1" $2 | awk '{print $1'} )
	# поиск с учетом ошибок написания если есть agrep
	[ -z $FIND -a  "$(which agrep 2>/dev/null)" ] && FIND=$( agrep -I 1 -D 1 -E 1 -w "$1" $2 | head -n 1 | awk '{print $1'})
	[ $FIND ] || return
	FIND=$(echo $FIND |sed 's/^_//')
	LIST=$(grep _${FIND} $2 |sed 's/^_[^\ ]*\ //')
echo ${FIND}:  "$LIST"
}

checksign() {
	local a ans
	for a in ${PUBKEYS}/*.gpg ;do
		if gpg --verify --keyring $a "$1" "$2" ;then
			echo "File $(basename $2) signed by $(basename $a)"
		return 0
		fi
	done
	echo "Incorrect signature in $(basename $1)"
	echo "Continue: (y/n)"
	read ans
	[ $ans != "y" -a $ans != "Y" ] && exit 1 
}

update_repolists() {
local file sign_file ans a 
n=1
for a in $(cat $mirror_list) ; do
	echo $a |grep -q "^[[:space:]]*#" && continue
	file='' ; sign_file=''
	if [ -d $a ] ; then
		file=$(mkrepolist $a ${WORK_DIR}/lists/${n}.list)
	elif [ -f $a ] ; then
		file=$(getfile $a ${WORK_DIR}/lists/${n}.list)
	else 
		file=$(getfile $a ${WORK_DIR}/lists/${n}.list)
		sign_file=$(getfile ${a}.sig ${WORK_DIR}/lists/${n}.list.sig)
		if ! [ -f "$file" ] ; then
			echo -e "Cannot download repository list \n $a"
			continue
		fi
		if [ -f "$sign_file" ] ; then
			echo "check $sign_file <--> $file"
			checksign $sign_file $file
		else
			echo "check $sign_file <--> $file"
			echo "Repository list was not signed, or signature was not found"
			echo "Continue: (y/n)"
			read ans
			[ $ans != "y" -a $ans != "Y" ] && exit 1 
		fi
		add_repo_path "$file" "$a"
	fi 
	[ "$GUIMODE" != "on" ] && echo "$a --> $file" 
	n=$(( $n + 1 ))
done
}

#добавляем пути до репы в реполист
#$1 локальный реполист
#$2 реполист с путем до сервера
add_repo_path() {
	local path 
	path=$(dirname $2)
	# если не нужны листы в формате gz можно sed -i (без временного файла)
	zcat $1 >/tmp/tmpfilelist 2>/dev/null || cat $1 >/tmp/tmpfilelist
	cat /tmp/tmpfilelist | sed -r '/^[a-z]{3,6}:\/\//!s#^#'$path'/#' >  $1
	rm -f /tmpfilelist
}
HLP() {
# Заглушка
cat $0 |egrep '\-.*\|.*\-\-.*\;\;' |sed 's/\"//g'
}
#################### К О Д ##############################
for arg in $@
do
  case "${arg}" in
    "-u" | "--update-media" ) UPDATE_M="on";;
    "-f" | "--force" ) FORCE="on";;
    "-i" | "--install" ) INSTALL="on"; LOAD="on";;
    "-l" | "--load" ) LOAD="on";;
    "-o" | "--outdir" ) outdir="on";;
    "-s" | "--search" ) SEARCH="on";;
    "-g" | "--guimode" ) GUIMODE="on";;
    "-m" | "--mkrepolist" ) MKREPOLIST="on";;
    "-h" | "--help")  HLP ;exit 1;;
    "-"*[A-Za-z]*) echo "$(basename "$0"): invalid option -- '$(echo ${arg} | tr -d '-')'" >&2; HLP; exit 1;;
    *) if [ "${outdir}" = "on" ]; then OUTDIR="${arg}"
       else sourcelist="${sourcelist} ${arg}"; fi
       outdir="off";;
  esac
done

[ "$GUIMODE" = "on" ] && DEVNULL=">/dev/null 2>&1"

# если --mkrepolist создаем _REPOLIST для указанной папки в ней же
# и выходим. Для создания реполистов на сервере. 
if [ "$MKREPOLIST" ] ; then
	mkrepolist $sourcelist
	exit
fi

[ "$SEARCH" ] || check_net 

mkdir -p ${WORK_DIR}/lists

# если ключ -u или холодный старт создаем списки
if [ "$UPDATE_M" == "on" -o ! -f ${WORK_DIR}/sfs-full-repolist ] ; then
	[ "$GUIMODE" != "on" ] && echo '#########################################################################'
	rm -rf ${WORK_DIR}/lists/*
	update_repolists
	make_full_repolist
	[ "$GUIMODE" != "on" ] && echo '#########################################################################' 
fi
[ -z "$sourcelist" ] && exit
#Обнуляем списки загрузки
:> ${WORK_DIR}/download.lst
:> ${WORK_DIR}/requires.lst
for reg in $sourcelist ; do
	unset MODPATH MODULE  MODULES MAYBE MD5
	AUFS_FOUND=$(aufs-n --raw '$bname_source' |grep -w "$reg")
	# если передано имя модуля, то достаем из него файл INFO и ищем NAME вместо поиска переданного
	# это для обновления 
	if [ -f "$reg" ] ; then 
		MODULE="$(basename $reg)"
		unsquashfs -d /tmp/info "$reg" -e ${PFSDIR}/INFO >/dev/null 2>&1
		[ -f /tmp/info/${PFSDIR}/INFO ] && reg=$(cat /tmp/info/${PFSDIR}/INFO |grep NAME |sed 's/NAME=//')
		rm -rf /tmp/info
	fi
	[ ! -f "$reg" ] && MODULE=$(find_mods $reg new)
	[ ! -f "$reg" ] && MODULES=$(find_mods $reg all)
	[ -z "$MODULE" ] && MAYBE=$(find_alias $reg $ALIASCFG)
	MODPATH="$(echo $MODULE |cut -d " " -f1)"
	echo $MODULE |grep -q 'MD5=' && MD5=$(echo $MODULE |sed 's/.*MD5=//' |cut -f1)
	if [ "$FORCE" = "on" ] ; then
		if [ -z "$MODULE" ] ; then
			echo "Модуль не найден, измените шаблон,повторите поиск"
			exit
		fi
		echo "$MODPATH" "$MD5" >> ${WORK_DIR}/download.lst
	elif [ "$GUIMODE" = "on" ] ; then
		echo "maybe:>> $MAYBE"   
		echo "new:>> $MODPATH"
		for b in $MODULES ; do echo "old:>> $b" ; done
	else
		if [ -n "$AUFS_FOUND" ] ; then
			# если подключен такой модуль предупреждаем
			echo "Модуль подходящий под шаблон $reg - $AUFS_FOUND подключен в данный момент"
			read qqq 
			[ "$qqq" == 'y' -o "$qqq" == 'Y' ] &&  exit $LINENO
		fi
		if [ -z "$MODULE" ] ; then
			if [ -z "$MAYBE" ] ; then
				echo "$reg Не найден в репозитории. Измените шаблон,повторите поиск"
			else
				echo "Возможно вы искали:"
				echo "==> $MAYBE"
				echo "Измените шаблон,повторите поиск"
			fi
		else
			if [ $(echo $MODULES |wc -w) -ge 1 ] ; then
				echo -e "\nПодходят под шаблон - \"$reg\":"
				for b in $MODULES ; do echo "==> $b" ; done
			fi
			echo ''
			echo -e "\nВероятно вы искали этот модуль:" 
			echo -n "==> "
			for item in $(echo $MODULE) ; do
				echo $item
			done
			[ $SEARCH ] || echo "загружать?  (Y/N)"
			if [ "$SEARCH" == "on" ] ; then d=Y ; else  read d ; fi
			if [ "$d" != Y -a "$d" != y ] ; then
				echo "Уточните шаблон поиска и повторите"
				continue
			fi
		fi
	fi
	[ -z "$MODPATH" ] || echo "$MODPATH" "$MD5" >> ${WORK_DIR}/download.lst
done

[ "$SEARCH" = "on" ] && exit
[ "$INSTALL" = "on" ] && TARGET_PATH=$INSTALL_DIR
[ "$OUTDIR" ] && TARGET_PATH=$OUTDIR
[ "$TARGET_PATH" ] || TARGET_PATH="./"
if ! [ -w $TARGET_PATH ] ; then
    echo "ERROR: '$TARGET_PATH' не доступен для записи"
    exit ${LINENO}
fi
cat ${WORK_DIR}/download.lst |while read a ;do
	MOD='' ; MD5='' ; CHECKMD5=no ; DMOD=''
	DMOD=$(echo $a |awk '{print $1}')
	MD5=$(echo $a |awk '{print $2}')
	MOD=$(getfile "$DMOD" $TARGET_PATH/$(basename $DMOD))
	if [ -n "$MD5" ] ; then 
		CHECKMD5=checked
		md5sum "$MOD" |grep -q $MD5 || CHECKMD5=error
	fi
	if  [ -f "$MOD" -a "$CHECKMD5" != "error" ] ; then
		[ "$GUIMODE" != "on" ] && echo "==> $(basename $MOD) - загружен успешно. (проверка md5 - $CHECKMD5)"
		[ "$GUIMODE" = "on" ] && echo "download:>> $MOD"
	else
		[ "$GUIMODE" != "on" ] && echo "==> $(basename $MOD) - ошибка загрузки!!! (проверка md5 - $CHECKMD5, $MD5)"
	fi
	get_requires $MOD 
	
	if [ "$LOAD" == "on" ] ; then 
		eval pfsload "$MOD" $DEVNULL
		if 	aufs-n --raw '$bname_source' |grep -wq "$(basename $MOD)" ; then
			[ "$GUIMODE" != "on" ] && echo "==> $(basename $MOD) - подключен успешно"
			[ "$GUIMODE" = "on" ] && echo "pfsload:>> $MOD"
		else
			[ "$GUIMODE" != "on" ] && echo "==> $(basename $MOD) - ошибка подключения!!!"
		fi
	fi
done
if ! [ -z "$(cat ${WORK_DIR}/requires.lst)" ] ;then
	reqlist=''
	allreqlist=$(cat ${WORK_DIR}/requires.lst)
	if ! [ -z "$allreqlist" ] ; then
	[ "$GUIMODE" != "on" ] && echo "Список зависимостей: $(echo $allreqlist)"
		for req in $(cat ${WORK_DIR}/requires.lst) ; do
			aufs-n --raw '$bname_source' |grep -wq "$req" && eval echo "$req - уже подключен" $DEVNULL && continue
			find $TARGET_PATH -maxdepth 1 -type f -name "*${req}*" |grep -wq "$req" && eval echo "$req - уже загружен" $DEVNULL && continue
			reqlist="$reqlist $req"
		done
	fi
	M='-f'; [ "$GUIMODE" = "on" ] && M='-g'
	L='' ; [ "$LOAD" = "on" ] && L="-l"
	if [ -n "$reqlist" ] ; then
		[ "$GUIMODE" != "on" ] && echo "Из них отсутствуют: $reqlist, загружаем"
		$0 $L $M -o $TARGET_PATH $reqlist
	fi  
fi
