#!/bin/bash
#
# __copy1__
# __copy2__
#
CMD=$(basename $0)
CMDVER="1.3"
CMDSTR="$CMD v$CMDVER (2020/01)"

set -e -u

Flist="$CMD.files.txt"

[ -f /etc/default/kusa-paths ] && . /etc/default/kusa-paths
KUSA_PATH_TMPDIR=${KUSA_PATH_TMPDIR:-/tmp}
export TMPDIR=${TMPDIR:-$KUSA_PATH_TMPDIR}

usage()
{
	echo "
== $CMDSTR == extended locate-like filelist database search/update ==

usage:	$CMD [options] search_expr	(search, using egrep regexp)
	$CMD [-u|--update] [options]	(update database REPLACING entries)
	$CMD [-a|--add] [options]	(update database ADDING entries)
	$CMD [-l|--list]		(list databases)
	$CMD [-L|--long-list]		(list databases w/details)
	$CMD [-i|--info] dbname		(show disk info, if any)


OPTIONS:

 -v|--verbose	be verbose (default)
 -q|--quiet	be quiet
 -D|--debug	debug messages
 --create-conf	create a default config file (\$HOME/.$CMD.conf) and exists
 --		stop args evalutation (usefull to pass arguments to egrep search)


SEARCH OPTIONS:
 --archives	search inside archives, too
 --files	search only files
 --dirs		search only directories


UPDATE OPTIONS:

 -d|--db filename
 	use 'filename' instead of default filename, .$CMD extension will
	be automatically added, if a single word is used the program will
	searches in the search path list for existing one, or take the
	first writeable place in the path list if creating a new one

 --tag tag
 	prepend 'tag|' to filenames in database, ie: use volume name for
	removable disks; for best results you should use it with
	--db tag --rmleaf

 --zip (--no-zip)
 	searches inside zipfiles
 --tar (--no-tar)
 	searches inside tarfiles
 --7z (--no-7z)
 	searches inside 7z files

 --rmleaf (--no-rmleaf)
	removes directories passed as arguments from filelist, usefull to wipe
	out mountpoint for removable disks; use with --tag VOLUMENAME

 --replace
 	replaces db file (default is update, not replace)

 --compress
 	compress db file (with gzip --fast)



EXAMPLES
  you have an usb disk mounted on /media/usbdisk, labeled UDISK001;
  to create a complete filelist of the disk in its own dbfile; using
  the --rmleaf option you can wipe out the mountdir \"/media/usbdisk\"
  from the db entries:

  	xlocate --update --tag UDISK001 --db UDISK01 --rmleaf
		/media/usbdisk

  after adding some directories to the disc, add them to the db; to
  avoid rescanning the whole disk you can scan only the new dirs, but
  take note that to do this you cannot use the --rmleaf option, because
  you need the dirs names to be in the recorded file paths; to leave
  out the mount dir you should cd in it, and records relative paths:

	cd /media/usbdisk
  	xlocate --update --add --tag UDISK001 --db UDISK001
		--no-rmleaf dir1 dir2 dir3 ... dirN


  note that $CMD is agnostic about the real tree that is scanning, the
  recorded entries are intifitied by the tag, than can be arbitrary
  and not really related to the current tree you are scanning/adding
  
  it's up to you to be consistent with tag names, dbfilaes names, and
  the paths you are scanning

  example: you are using a temp directory to store files that will be
  copied (added) to an external disk; you can copy the files, and run
  $CMD in the temp dir, faster than the usb disk, before cleaning it,
  just make sure that you use the --add and --no-rmleaf options, and
  to use $CMD-diskinfos to update the extended disk infos:

	cd /tmp/mytempdir
	cp -avf * /media/usbdisk/

	cd /media/usbdisk
	$CMD-diskinfos
	cp disk-usage.txt /mnt/mytempdir

	cd /tmp/mytempdir
  	xlocate --update --add --tag UDISK001 --db UDISK001
		--no-rmleaf *



CURRENT SETTINGS:
" >&2
	print_current_settings >&2

	[ $# != 0 ] && { echo "$@" >&2; echo >&2; }
	exit 1
}

cleanup()
{
	[ "x${TmpDir:-}" != "x" ] && {
		if $DEBUG
		then
			decho "NOT removing TmpDir ($TmpDir)" >&2
		else
			rm -rf "$TmpDir"
		fi
	}
	return 0
}

print_current_settings()
{
	local text="
  Mode:		  $Mode
  Cfg paths:      $Config_searchpath
  Db paths:       $Db_searchpath
  DbFile	  $DbFile
  F_autotag	  $F_autotag
  F_remove_leaf:  $F_remove_leaf
  Scan archives:  zip=$F_search_zip tar=$F_search_tar 7z=$F_search_7z
"
	if [ "X${PRJ:-}" = "X" ]
	then
		echo "$text" | sed -e "s#$HOME#\$HOME#g"
	else
		echo "$text" | sed -e "s#$PRJ#\$PRJ#g" -e "s#$HOME#\$HOME#g"
	fi
	return 0
}



COLUMNS=$(tput cols)
COLUMNS=${COLUMNS:-80}
COLUMNS=$(expr $COLUMNS - 10)


echotrunc() { echo "$@"; }
decho() { $DEBUG && { echo -n "D# " >&2; echo "$@" >&2; } || :; }
vecho() {
	$VERBOSE || return 0
	local n=
	[ "X$1" = "X-n" ] && { n="-n"; shift; }
	[ "X$1" = "X-e" ] && { shift; }
	local msg=$(echotrunc $COLUMNS "$@")
	echo -e $n "$msg" >&2
}

[ -f /lib/ku-base/echo.sh ] && . /lib/ku-base/echo.sh


get_volumename()
{
	# not yet implemented
	:
}

scan_tarfile()
{
	local dir=$1
	local tag=$2
	local file=$(echo "$3" | sed -e "s}^$tag|f|}}")

	local size=
	local date=
	local time=
	local fname=
	local outfile="$TmpDir/tar.out"
	local cachefile=
	local status=0

	vecho -n "   TAR '$file' ... "

	[ -f "$file" ] || file="$dir/$file"

	cachefile=$(locate_cachefile "$file")

	if [ -s "$cachefile" ]
	then
		vecho -n "(cached) ... "
	else
		tar tf "$file" >"$outfile" 2>&1 || status=$?

		gzip --fast <"$outfile" >"$TmpDir/cachefile"
		mv "$TmpDir/cachefile" "$cachefile"
	fi

	gzip -d < "$cachefile" | sed -e "s<^<$tag|a|$file$ArcTag<"

	if [ $status = 0 ]
	then
		vecho "ok"
	else
		echo "$tag|a|$file$ArcTag **** ERROR $status READING TARFILE ****"
		vecho "**ERROR**"
		$VERBOSE && cat "$outfile" >&2
	fi
	rm -f "$outfile"
	return 0
}

scan_zipfile()
{
	local dir=$1
	local tag=$2
	local file=$(echo "$3" | sed -e "s}^$tag|f|}}")
	local outfile="$TmpDir/zip.out"

	local size=
	local date=
	local time=
	local fname=
	local cachefile=
	local status=0

	vecho -n "   ZIP '$file' ... "

	[ -f "$file" ] || file="$dir/$file"

	cachefile=$(locate_cachefile "$file")

	if [ -s "$cachefile" ]
	then
		vecho -n "(cached) ... "
	else
		unzip -l "$file" >"$outfile" 2>&1 || status=$?
		vecho -n ". "

		gzip --fast <"$outfile" >"$TmpDir/cachefile"
		vecho -n ". "

		mv "$TmpDir/cachefile" "$cachefile"
	fi

	# unzip entry sample line
	#   7091  1998-07-03 06:49   BLOCK/_______9.dwg
	#
	gzip -d < "$cachefile" | while read size date time fname
	do
		case $fname in
		  Name|----|""|*/) continue ;;
		  *) echo "$tag|a|$file$ArcTag$fname" ;;
		esac
	done

	if [ $status = 0 ]
	then
		vecho "ok"
	else
		echo "$tag|a|$file$ArcTag **** ERROR $status READING ZIPFILE ****"
		vecho "**ERROR**"
		$VERBOSE && cat "$outfile" >&2
	fi
	rm -f "$outfile"
	return 0
}


scan_7zfile()
{
	local dir=$1
	local tag=$2
	local file=$(echo "$3" | sed -e "s}^$tag|f|}}")
	local outfile="$TmpDir/7z.out"

	local date=
	local time=
	local attr=
	local size=
	local fname=
	local skip=
	local cachefile=
	local status=0

	vecho -n "   7Z  '$file' ... "

	[ -f "$file" ] || file="$dir/$file"

	cachefile=$(locate_cachefile "$file")

	if [ -s "$cachefile" ]
	then
		vecho -n "(cached) ... "
	else

		7z l "$file" >"$outfile" 2>&1 || status=$?
		vecho -n ". "

		gzip --fast <"$outfile" >"$TmpDir/cachefile"
		vecho -n ". "

		mv "$TmpDir/cachefile" "$cachefile"
	fi

	# unzip entry sample lines
	#    Date      Time    Attr         Size   Compressed  Name
	# ------------------- ----- ------------ ------------  ------------------------
	# 2014-01-17 10:42:46 ....A          471        27825  newsletter/newsletter.vmdk
	# 2014-01-17 10:30:34 ....A       107499               newsletter/vmware-1.log
	# 2014-01-17 16:48:47 D....            0            0  newsletter
	#
	# parsing is tricky, due the variable fields number (csize is not always
	# present), I don't want to rely on fixed column size, so evaluate line-by-line
	#
	skip=true
	gzip -d < "$cachefile" | while read date time attr size csize fname
	do
		# skip all to first header separator
		case $date$time in
		  ------------------*)
		  	$skip || break
		  	skip=false
			continue
			;;
		esac
		$skip && continue

		case $attr in
		  D*)	continue ;;	# do not list directories
		esac
		if [ "X$fname" = "X" ]
		then
			# csize is not present
			echo "$tag|a|$file$ArcTag$csize"
		else
			echo "$tag|a|$file$ArcTag$fname"
		fi
	done

	if [ $status = 0 ]
	then
		vecho "ok"
	else
		echo "$tag|a|$file$ArcTag **** ERROR $status READING 7Zip FILE ****"
		vecho "**ERROR**"
		$VERBOSE && cat "$outfile" >&2
	fi
	rm -f "$outfile"
	return 0
}




# uses first 50Mb of input file to generate a md5sum used
# as cache filename; content will be compressed
#
locate_cachefile()
{
	local inputfile=$1
	local cachedir="${WriteableDb/.xlocate/.cache}"
	local filename=$(fast_fingerprint "$inputfile")

	[ -d "$cachedir" ] || {
		mkdir "$cachedir" || return 1
		decho "created cachedir '$cachedir'"
	}
	filename=${filename/ */}

	decho "cachefile is '$cachedir/$filename'"
	echo "$cachedir/$filename"
	return 0
}



do_list_databases()
{
	local dbfile=${1:-}
	local dir=
	local ifs="$IFS"
	local files=
	local found=

	case $dbfile in
	  /*)	# absolute path, list only this, if present
		#
		get_db_filename "$dbfile" ] || {
			echo "  db file '$dbfile' not found"
			return 1
		}
		return 0
		;;

	  "")	# none, list all databases in searchpath
		#
		found=false
		IFS=":"
		for dir in $Db_searchpath
		do
		 	files=$(ls $dir/*.$CMD $dir/*.$CMD.gz 2>/dev/null) || :
			[ "X$files" = "X" ] && continue
		 	echo "$files"
		 	found=true
		done
		IFS="$ifs"
		$found || echo " (no database files found in searchpath)" >&2
		;;

	  *)	# relative path, search first occurence using $Db_searchpath
		#
		IFS=":"
		for dir in $Db_searchpath
		do
			get_db_filename "$dir/$dbfile" || continue
			found=true
			break
		done
		IFS="$ifs"
		$found || echo "  db file '$dbfile' not found in searchpath"
		;;
	esac
	return 0
}

do_long_list_databases()
{
	local dbfile=
	local entries=
	local infofile=


	do_list_databases "$@" >/dev/null || return $?

	do_list_databases "$@" | while read dbfile
	do
		echocr " scanning $dbfile ... "
		case $dbfile in
		  *.gz)	cnt=$(gzip -d <"$dbfile" | wc -l) ;;
		  *)	cnt=$(cat "$dbfile" | wc -l) ;;
		esac
		echocr ""
		infofile=${dbfile/.gz/}
		infofile="${infofile/.$CMD/}.usage"
		printf "%8d entries  " "$cnt"
		if [ -f "$infofile" ]
		then
			echo -n "I  "
		else
			echo -n "-  "
		fi
		ls -l "$dbfile"
	done
	return 0
}





do_show_info()
{
	local dbfile=

	dbfile=$(do_list_databases "$1") || return $?
	dbfile=${dbfile/.$CMD.gz/}
	dbfile=${dbfile/.$CMD/}

	[ -f "$dbfile.usage" ] || {
		echo "  info for tag '$1' not exists" >&2
		return 1
	}
	cat "$dbfile.usage"
}





do_search()
{
	local ifs="$IFS"
	local dir=
	local stat=1
	local found=false
	local file=
	local okfile="/tmp/ok-$$"

	case $DbFile in
	  /*)	# absolute path, use only this if present
	  	get_db_filename "$DbFile" >/dev/null || {
			echo "  dbfile '$DbFile' not found" >&2
			return 1
		}
		echocr " scanning $DbFile ...\n"
		search_in_dbfile "$DbFile" "$@"
		stat=$?
		echocr ""
		;;
	  "")	# none, search all files in $Db_searchpath
	  	do_list_databases >/dev/null || return 1
		rm -f $okfile
		do_list_databases | while read file
		do
			echocr " scanning $file ...\n"
			decho "searching '$file'"
			search_in_dbfile "$file" "$@" && touch $okfile
			echocr
		done
		[ -f $okfile ] && stat=0
		rm -f $okfile
		;;
	  *)	# relative path, search using $Db_searchpath
		IFS=":"
		for dir in $Db_searchpath
		do
			get_db_filename "$dir/$DbFile" >/dev/null || continue

			echocr " scanning $dir/$DbFile ...\n"
			decho "searching '$dir/$DbFile'"
			search_in_dbfile "$dir/$DbFile" "$@"
			echocr ""
			stat=0
			found=true
			break
		done
		IFS="$ifs"
		$found || echo " (no database files found in searchpath)" >&2
		;;
	esac
	return $stat
}

get_db_filename()
{
	local dbfile=$1
	local ext=

	for ext in "" .gz .$CMD .$CMD.gz
	do
		[ -f "$dbfile$ext" ] && {
			echo "$dbfile$ext"
			return 0
		}
	done
	return 1
}

search_in_dbfile()
{
	local file=
	file=$(get_db_filename "$1") || return 1
	shift

	case "$file" in
	  *.gz)	gzip -d <"$file" | egrep "$@" | filter_output ;;
	  *)	egrep "$@" "$file" | filter_output ;;
	esac
	return 0
}

filter_output()
{
	local filter_exclude="| fgrep -v '$ArcTag'"
	local filter_include=

	$F_search_archives	&& filter_exclude=
	$F_search_files		&& filter_include="| fgrep '|f|'"
	$F_search_dirs		&& filter_include="| fgrep '|d|'"

	eval cat $filter_exclude $filter_include
}






do_update()
{
	local ifs="$IFS"
	local dir=
	local file=
	local out=
	local tag=
	local leaf=
	local cntfmt="%-12s %8d %s\n"
	local cnt=
	local flist=
	local originaldb=
	local infofile=

	[ "x$WriteableDb" == "x" ] && {
		WriteableDb=$(search_writeable_dbfile) || {
			echo "can't find a writeable dbfile" >&2
			return 1
		}
		WriteableDb=${WriteableDb/.gz}
	}
	out=$(basename "$WriteableDb")
	out="$TmpDir/$out"

	[ "X$Tag" != "X" ] && tag="$Tag"

	print_current_settings

	vecho " updating dbfile: $WriteableDb"
	for dir
	do
		[ -d "$dir" ] || {
			echo "  dir '$dir' not found" >&2
			continue
		}

		if $F_remove_leaf
		then
			leaf=
			decho "remove leaf '$dir/'"
		else
			leaf="$dir/"
			decho "don't remove leaf '$leaf'"
		fi

		vecho -n "  scanning '$dir' ... "
		find "$dir" -type d | sed -e "s}^$dir/}$leaf}" -e "s}^}$tag|d|}" >>"$out" || :
		find "$dir" -type f | sed -e "s}^$dir/}$leaf}" -e "s}^}$tag|f|}" >>"$out" || :
		##vecho -n " (attr) ... "
		##find "$dir" -type f -print0 | xargs -0 stat --format "%n|%s|%a|%U|%G|%Y|%N" \
			##| sed -e "s}^$dir/}$leaf}" -e "s}^}$tag}" -e "s/|'/|/' -e "s/'$//" \
			##>>"$out.attrs"
		vecho "ok"
		$F_search_tar && {
			flist="$TmpDir/tarfiles"
			egrep "\.tar$|\.tar.gz$|\.tar.bz2$|\.taz$" "$out" \
				| fgrep -v "$ArcTag" \
				| fgrep '|f|' \
				> "$flist" || :
			decho $(wc -l "$flist")
			while read file
			do
				scan_tarfile "$dir" "$tag" "$file" >>$out || :
			done <"$flist"
		}
		$F_search_zip && {
			flist="$TmpDir/zipfiles"
			egrep "\.zip$|\.zip.001$" "$out" | fgrep -v "$ArcTag" >"$flist" \
				| fgrep -v "$ArcTag" \
				| fgrep '|f|' \
				> "$flist" || :
			decho $(wc -l "$flist")
			while read file
			do
				scan_zipfile "$dir" "$tag" "$file" >>$out || :
			done <"$flist"
		}
		$F_search_7z && {
			flist="$TmpDir/7zfiles"
			egrep "\.7z$|\.7z.001$" "$out" | fgrep -v "$ArcTag" >"$flist" \
				| fgrep -v "$ArcTag" \
				| fgrep '|f|' \
				> "$flist" || :
			decho $(wc -l "$flist")
			while read file
			do
				scan_7zfile "$dir" "$tag" "$file" >>$out || :
			done <"$flist"
		}

		[ "X$Tag" != "X" ] && {
			infofile=
			[ -f "$dir/disk-usage.txt" ] && infofile="$dir/disk-usage.txt"
			[ -f "./disk-usage.txt" ] && infofile="./disk-usage.txt"
			[ "X$infofile" != "X" ] && {
				vecho " copying $infofile -> $Tag.usage"
				cp "$infofile" "$TmpDir/$Tag.usage"
			}
		}
	done

	$VERBOSE && {
		cnt=0
		[ -f "$WriteableDb.gz" ] && cnt=$(gzip -d <"$WriteableDb.gz" | wc -l)
		[ -s "$WriteableDb" ] && cnt=$(wc -l <"$WriteableDb")
	}

	:>"$out.clean"

	if $F_ReplaceDbFile
	then
		vecho "  will replace '$WriteableDb' ... ok"
	else
		if [ $Mode = "update" ]
		then
			vecho -n "  removing old entries tagged '$Tag' from '$WriteableDb' ... "
			if [ -f "$WriteableDb.gz" ]
			then
		  	  gzip -d < "$WriteableDb.gz" | grep -v "^$Tag|" >"$out.clean" || :
			elif [ -f "$WriteableDb" ]
			then
		  	  grep -v "^$Tag|" "$WriteableDb" >"$out.clean" || :
			fi
			vecho "ok"
		else
			vecho -n "  will add entries in '$WriteableDb' ... "
			if [ -f "$WriteableDb.gz" ]
			then
		  	  gzip -d < "$WriteableDb.gz" >"$out.clean" || :
			elif [ -f "$WriteableDb" ]
			then
		  	  cat "$WriteableDb" >"$out.clean" || :
			fi
			vecho "ok"
		fi
	fi

	vecho -n "  sorting entries ... "
	sort -u "$out.clean" "$out" >"$out.sorted"
	vecho "ok"

	$VERBOSE && {
		echo
		printf "$cntfmt" "original:" "$cnt" "$WriteableDb"
		[ $Mode = "update" ] && printf "$cntfmt" " after wipe:" $(wc -l <"$out.clean") ""
		printf "$cntfmt" " updated:" $(wc -l <"$out") "$out"
		echo
		printf "$cntfmt" "actual:" $(wc -l <"$out.sorted") "$WriteableDb"
	}

	cat "$out.sorted" >"$WriteableDb" 
	rm -f "$out" "$out.sorted" "$out.clean"

	$F_CompressDbFile && {
		vecho -n "\n compressing dbfile ... "
		rm -f "$WriteableDb.gz"
		gzip --fast "$WriteableDb"
		vecho " ok"
	}

	[ -f "$TmpDir/$Tag.usage" ] && {
		vecho -n " saving $Tag.usage ... "
		file=${WriteableDb/.$CMD/.usage}
		cp "$TmpDir/$Tag.usage" "$file"
		vecho "ok"
	}

	return 0
}


is_writeable()
{
	local file=$1
	local dir=$(dirname "$1")

	[ -d "$dir" ] || return 1

	if [ -f "$file" ]
	then
		[ -w "$file" ] && return 0
	else
		( :>"$file" ) 2>/dev/null || return 1
		rm -f "$file"
		return 0
	fi
	return 1
}


search_writeable_dbfile()
{
	local ifs="$IFS"
	local dir=
	local found=
	local will_try=
	local files=

	case "$DbFile" in
	  /*)	# absolute path, try to use it as-is (except for the .gz ext)
		found=${DbFile/.gz/}
		found="${found/.$CMD/}.$CMD"
		$F_CompressDbFile && found="$found.gz"
		is_writeable "$found" || found=
		;;
	  "")	# none, fallback
	  	will_try="$DefaultDbFile"
	  	;;
	  */*)	# relatitve path, valid only if already present, take first
	  	if files=$(do_list_databases | grep "$DbFile")
		then
			found=$(echo "$files" | head -1)
			found=${found/.gz/}
			found="${found/.$CMD/}.$CMD"
			$F_CompressDbFile && found="$found.gz"
			is_writeable "$found" || found=
		else
			echo " cant find '$DbFile' to write on" >&2
			return 1
		fi
		;;
	  *)	# single word, search if already present and take first
	  	fname=$(do_list_databases | grep "/$DbFile") && {
			found=$(echo "$fname" | head -1)
			found=${found/.gz/}
			found="${found/.$CMD/}.$CMD"
			$F_CompressDbFile && found="$found.gz"
			is_writeable "$found" || found=
		}
		will_try="$DbFile"
	esac

	[ "X$found" != "X" ] && {
		echo "$found"
		return 0
	}

	# fallback?
	# uses first writeable location
	[ "X$will_try" != "X" ] && {
		IFS=":"
		for dir in $Db_searchpath
		do
			fname="$dir/${will_try/.$CMD/}.$CMD"
			$F_CompressDbFile && fname="$fname.gz"
			is_writeable "$fname" || continue
			found="$fname"
			break
		done
		IFS="$ifs"
	}

	[ "X$found" = "X" ] && return 1
	echo "$found"
	return 0
}


create_default_config_file()
{
	local out="$HOME/.$CMD.conf"

	[ -f "$out" ] && {
		echo "error: default config file '$out' already exists" >&2
		return 1
	}

	echo "# $CMD.conf

#------------------------------------------------------------------------------
# DB SEARCHPATH options
#------------------------------------------------------------------------------
#
# the searchpath is a columns (\":\") separatad list of directories where the
# dbfiles are stored and searched

# use this to add search directories before the standard
#
#Db_searchpath_before=

# use this to add search directories after the standard
#
#Db_searchpath_after=

# use this to override the standard searchpath
#
#Db_searchpath_override=


#------------------------------------------------------------------------------
# UPDATE options
#------------------------------------------------------------------------------

# removes directory path passed as argument from filelist
# ie: usefull to wipe out mountpoints for removable disks
#
# options: --rmleaf | --no-leaf
#
#F_remove_leaf=$F_remove_leaf

# automatically uses VOLUMENAME as tag if mountpoint detected
# on arguments directories
#
# options: --autotag
#
#F_autotag=$F_autotag

# searches inside archive files?
#
# options: --zip | --no-zip
#	   --tar | --no-tar
#	   --7z | --no-7z
#
#F_search_zip=$F_search_zip
#F_search_tar=$F_search_tar
#F_search_7z=$F_search_7z

# should compress dbfiles?
#
#F_CompressDbFile=$F_CompressDbFile

#--eof $out
" >"$out"
	echo "default config file '$out' created"
	return 0
}



do_autotagging()
{
	local dir=$1
	local dev=
	local label=

	dev=$(grep " $dir " /proc/mounts) || return 0
	label=$(blkid $dev | sed -e 's/.*: //')
	label=$( (eval $label; echo ${LABEL:-}) )

	[ "X$label" = "X" ] && return 0

	[ "X$DbFile" = "X" ]	&& { DbFile=$label; vecho "  autotag: using '$label' as DbFile"; }
	[ "X$Tag" = "X" ]	&& { Tag=$label; vecho "  autotag: using '$label' as Tag"; }

	return 0
}




# (MAIN)

PRJ=${PRJ:-}

Db_searchpath="$HOME/etc/$CMD"
Db_searchpath_before=
Db_searchpath_after=

if [ "x$PRJ" != "x" ]
then
	Config_searchpath="/etc/$CMD.conf:$PRJ/etc/$CMD.conf:$PRJ/.$CMD.conf"
	Db_searchpath="$Db_searchpath:$PRJ/etc/$CMD:/var/lib/$CMD"
else
	Config_searchpath="/etc/$CMD.conf"
	Db_searchpath="$Db_searchpath:/var/lib/$CMD"
fi
Config_searchpath="$Config_searchpath:$HOME/etc/$CMD.conf:$HOME/.$CMD.conf"
WriteableDb=
DbFile=
DefaultDbFile="_default.$CMD"

# FROM CONFIG FILE(s)
#
F_remove_leaf=false
F_autotag=true
F_search_zip=false
F_search_tar=false
F_search_7z=false

F_search_archives=false
F_search_files=false
F_search_dirs=false

VERBOSE=true
DEBUG=false
DBGLEVEL=0

Tag=
Mode="search"
F_ReplaceDbFile=false
F_CompressDbFile=false

ArcTag=">_>"	# used to mark files inside archives


# early options eval, just verbose, debug and config search directives
#
for arg
do
  case $arg in
    -v|--verbose)	VERBOSE=true ;;
    -q|--quiet)		VERBOSE=false ;;
    -D|--debug)		DEBUG=true ;;
    -c|--config)	shift; [ $# = 0 ] && usage "--config needs a filename"
    			[ -f "$1" ] || usage "config file '$1' not found"
			Config_searchpath="$Config_searchpath:$1"
			;;
    --)			break ;;
  esac
done

if $VERBOSE
then
	. /lib/ku-base/echo.sh
else
	# dummy func
	echocr() { :; }
fi

# and then, load config files
#
ifs="$IFS"
IFS=":"
for file in $Config_searchpath
do
	[ -f "$file" ] || continue
	decho "loading config file '$file'"
	. "$file"
done
IFS="$ifs"

[ "X$Db_searchpath_before" != "X" ]	&& Db_searchpath="$Db_searchpath_before:$Db_searchpath"
[ "X$Db_searchpath_after" != "X" ]	&& Db_searchpath="$Db_searchpath:$Db_searchpath_after"


# eval remaining options
#
while [ $# != 0 ]
do
  case $1 in
    -v|--verbose)	;;
    -q|--quiet)		;;
    -D|--debug)		;;

    --create-conf)	create_default_config_file; exit $? ;;

    -l|--list)		Mode="list" ;;
    -u|--update)	Mode="update" ;;
    -a|--add)		Mode="add" ;;
    -L|--long-list)	Mode="longlist" ;;
    -i|--info)		Mode="info" ;;

    --tar)		F_search_tar=true ;;
    --no-tar)		F_search_tar=false ;;
    --zip)		F_search_zip=true ;;
    --no-zip)		F_search_zip=false ;;
    --7z)		F_search_7z=true ;;
    --no-7z)		F_search_7z=false ;;

    --rmleaf)		F_remove_leaf=true ;;
    --no-rmleaf)	F_remove_leaf=false ;;
    --replace)		F_ReplaceDbFile=true ;;
    --compress)		F_CompressDbFile=true ;;
    -d|--db)		shift; [ $# = 0 ] && usage "--db requires an argument"
    			DbFile=$1
			;;
    --tag)		shift; [ $# = 0 ] && usage "--tag requires an argument"
    			Tag=$1
			;;
    --autotag)		F_autotag=true ;;

    --archives)		F_search_archives=true ;;
    --files)		F_search_files=true ;;
    --dirs)		F_search_dirs=true ;;

    --)			shift; break ;;
    -*|"")		usage "wrong paramenter: '$1'" ;;
    *)			break ;;
  esac
  shift
done



trap 'echo -e "\n*INTR*\n"; exit 255' 1 2 3
trap 'echo -e "\nunexpected error $? at $LINENO\n"' ERR
trap "cleanup" EXIT

case $Mode in
  list)
  	[ "X$Tag" != "X" ]	&& usage "option --tag incompatible with mode '$Mode'"
	do_list_databases "$@" || exit $?
	exit $?
	;;
  longlist)
  	[ "X$Tag" != "X" ]	&& usage "option --tag incompatible with mode '$Mode'"
  	do_long_list_databases "$@" || exit $?
	;;
  info)
  	[ $# != 1 ]		&& usage "'$Mode' mode requires exactly one argument"
	do_show_info "$1" || exit $?
	;;
  search)
  	[ "X$Tag" != "X" ]	&& usage "option --tag incompatible with mode '$Mode'"
	[ $# = 0 ]		&& usage "'$Mode' mode requires a search expression"
	do_search "$@" || exit $?
	;;
  add|update)
	[ $# = 0 ]		&& usage "'$Mode' mode needs at least one directory to scan"
	if $DEBUG
	then
		TmpDir="$TMPDIR/$CMD-tmp"
		rm -rf "$TmpDir"
		mkdir "$TmpDir"
		decho "TmpDir=$TmpDir" >&2
	else
		TmpDir=$(mktemp -d "$TMPDIR/$CMD-XXXXXXXX")
	fi
	$F_autotag && do_autotagging "$1"

	# atm, update is possibile only with Tag defined
	[ "X$Tag" = "X" ] && ReplaceDbFile=true

  	do_update "$@" || exit $?
  	;;
esac

exit 0
