#!/bin/bash
#                       /usr/local/bin/name_tidy+
# https://crystalfaeries.net/posix/bin/name_tidy+
# celeste:crystalfaery NAME_TIDY+ DEVELOPMENT VERSION 2020-08-30 21:25:01+00:00
# 2012-06-28 10:23:31+00:00 This only handles $1, not $2-$n so please rewrite it soon :-)
# We tidy the names of our arguments, their contents, or current directory
# We SHOULD accept files as arguments versus demanding a whole directory
# We presume to do the current directory or the one named as argument (after optional recursion flag -r)
# 2011-10-08 19:14:20+00:00 had to hack the file renaming logic to accomodate the NTFS handling of case
# 2011-11-14 00:39:48+00:00 fixing bug with rsyncing an empty non cannonically named dir over its empty cannonically named empty dir.
# 2013-10-03 18:20:15+00:00 use "set -P" to avoid following symlinks
set -P	# do not follow symlinks (####### THIS DOES NOT WORK! WE DO FOLLOW SYMLINKS!	#######)
#		  ONLY UPON ACTIVATION OF A SWITCH OPTION SHOULD WE FOLLOW SYMLINKS	BUG!
# 2014-03-18 10:05:15+00:00 filter default directory files {HEADER,README}.{html,txt}
# 2015-08-02 11:19:52+00:00 we noticed we failed on a directory named "_".
# 2015-10-04 18:29:57+00:00 we are not yet handling names beginning with a "-". (hack added at end)
# 2016-07-27 11:46:53+00:00 we finally handle a Run_Command_file per directory to block name_tidy operations there!
#				(echo "no" > .name_tidy)	will block name_tidy in cwd.
#	future option?		(echo "norecurse" > .name_tidy) could block recursion into subdirs?
# streamripper generates " - .mp3" for a stream lacking metadata
let help=20	# line_number_within_file - 1

let recurse=1	# false	by default: do not recurse into subdirectories, tidy only current working directory or if specified $@
selection=""	# null	by default: record name_tidy depth selection as hidden file in this directory
case $# in
0)	:	# no options nor arguments, therefore we should non-recursively tidy the current working directory
	;;	# fall through to main code :-)
*)	:	# we have one or more options or arguments
	case "${1}" in
	-v | --version)	:	# output my version number and quit
		head -n 4 $0 | tail -n 1
		exit
		;;
	-h | --help )	:	# output help and quit
		head -n $help $0
		exit
		;;
	-y | --yes )	:	# mark the current working directory		to be tidied including possible recursion into subdirectories
		selection="yes"	# this is the default file content and default if the file or its content is missing
		shift
		;;
	-n | --no )	:	# mark the current working directory not	to be tidied at all
		selection="no"	# do not mess with this directory at all (other than our selection file .name_tidy in it)
				# REMEMBER THAT IF YOU CAN'T MARK THE DIRECTORY BY PLACING OUR HIDDEN FILE INSIDE OF IT,
				# YOU CAN ADD THE DIRECTORY TO YOUR PER-USER GLOBAL BLOCK-LIST
		shift
		;;
	--no_recurse ) :	# mark the current working directory not	to have subdirectories tidied
		selection="no_recurse"
		shift
		;;
	-r | --recurse)	:	# tidy recursing into subdirectories
		let recurse=0	# true  by option - do recurse into subdirectories
		shift		# finished option
		;;
	*)		:	# anything else should be directories to work in as arguments on commandline
		;;
	esac
	;;	# we done with options, may have arguments (which dirs to tidy as $@)
esac

# Is this first execution for this user? We must default to safety...
if [ \!	-r					"${HOME}"/.name_tidy_exclude.txt ]
then	# generate a list of system directories to NEVER name_tidy:
    if [ -d	/usr/share/rear/skel/default/ ]
    then
        cd	/usr/share/rear/skel/default/	&& \
        for	d	in $(find .	-mindepth 1 -maxdepth 1 -type d	| sort | sed 's/^\.//')
        do
	    echo $d >>	/tmp/exclude.$$.txt
        done
    fi
    pushd	/	1>/dev/null	2>/dev/null
    for		d	in $(find .	-mindepth 1 -maxdepth 1 -type d	| sort | sed 's/^\.//')
    do
	    echo $d >>	/tmp/exclude.$$.txt
    done
    popd		1>/dev/null	2>/dev/null
    sort -u		/tmp/exclude.$$.txt >	$HOME/.name_tidy_exclude.txt	\
    &&	rm		/tmp/exclude.$$.txt					\
    ||	exit	$?	# Fail Safe
fi
name_tidy_exclude="$HOME/.name_tidy_exclude.txt"	# System-Wide Exclude List for Executing User

if [ "${1}" ]			# any target directory arguments on command line?
then
	if [ -d "${1}" ]	# other arguments should be directories to process
	then
		cd -P "${1}"	|| exit -1	# make next directory to process be current working directory
		shift		# finished with argument
	else
		if [ -h "${1}" ]	# other arguments could be symlinks to directories to process
		then
			cd -P "${1}"	|| exit -1	# make next directory to process be current working directory
			shift		# finished with argument
		else
			echo	"$0: ERROR (neither dir nor symlink): ${1}."		1>&2
			exit -1		# better to die then do the wrong thing
		fi
	fi
else
	cd -P .			# force physical location
fi

directory="`pwd`"		# our physical location

#	is this directory excluded by our user's system-wide exclude list?
if [ -r "${name_tidy_exclude}" ]
then	# we have an exclude list
	if [	grep	"${directory}"	"${name_tidy_exclude}"	>&	/dev/null	]
	then	# we are NOT to name_tidy this directory (nor its subdirectories unless invoked below this level)
		echo ""									1>&2
		echo "NOT	tidying $directory due to global Exclude file"	1>&2
		exit	0	# gracefully decline to work here
	fi
fi

case "${selection}" in
	#	does this directory contain a "run control" file denying our tidy operation here?
	RC=""	# as simple as we can make it (for now)
	if [ -r .name_tidy ]
	then	# we have a "run control" file applicable to this directory
		RC="`head -n 1 .name_tidy`"	# as simple as we can make it (for now)
		if [ "${RC}" = "no" ]
		then	# we are NOT to name_tidy this directory (nor its subdirectories unless invoked below this level)
			echo ""									1>&2
			echo "NOT	tidying $directory due to local Run Control file"	1>&2
			exit
		fi
	else	# we may be making a "run control" file in this directory
		*) case "${selection}" in
			yes)	:
				;;
		no_recurse)	:
				;;
			no)	:
				;;
			"")
				echo	"	tidying $(cwd)"					1>&2
				;;
			*)
				echo	"	tidying $directory"				1>&2
				;;
	   	esac
		
	fi

#############################################################################################################
# start by expunging recursive directory hierarchies... callously discarding contents of insane structures...
no_recurse	# follow the White Rabbit, Neo	(DANGER! DANGER!, Will Robinson)

let loop=1		# start the loop
while [[ $loop -gt 0 ]] ; do
    let loop=0		# end the loop when we make no changes in a pass

    # start by fixing names that look like options to commands:
    for f in $(find . -maxdepth 1 -name '-*' 2>/dev/null | sed 's/^\.\/-*//g')
    do	# iterate
	nice ionice -c 3 rsync -auvzH ./-*"${f}" "${f}" && rm -rf ./-*"${f}" || echo "$0:${directory}/-*${f}"	1>&2
    done

	mv -i " - .mp3" $(now -t).mp3 2>/dev/null	# StreamRipper creates these when lacking metadata in stream (KKCR)

    for i in *; do	# iterate through each item in current working directory
			# Files are renamed, though .* files are untouched
	if   [ "$i" == "gPodder" ]	# gPodder database
		then continue		# leave gPodder     files alone
	elif [ "$i" == "CACHEDIR.TAG" ]	# see /var/cache/CACHEDIR.TAG
		then continue		# leave CACHEDIR.TAG files alone
	elif [ "$i" == "HEADER.txt" ]	# default directory files
		then continue		# leave HEADER.html files alone
	elif [ "$i" == "HEADER.html" ]	# default directory files
		then continue		# leave HEADER.html files alone
	elif [ "$i" == "README.html" ]	# default directory files
		then continue		# leave README.html files alone
	elif [ "$i" == "README.txt" ]	# default directory files
		then continue		# leave README.html files alone
	elif [ "$i" == "README" ]	# default directory files
		then continue		# leave README files alone
	elif [ "$i" == "Incomplete Downloads" ]	# do not interfere with downloads currently in progress
		then continue		# leave Incomplete Downloads directory alone
	elif [ "$i" == "Converted" ]	# do not alter our Converted folder name
		then continue		# leave Converted directory alone
	elif [ "$i" == "Downloads" ]	# do not alter our Downloads
		then continue		# leave downloads directory alone
	elif [ "$i" == "Desktop" ]	# do not alter our Desktop directory
		then continue		# leave downloads directory alone
	elif [ "$i" == "Mail" ]		# do not alter our e-Mail directory
		then continue		# leave downloads directory alone
	elif [ "$i" == "Screencasts" ]	# do not alter our Screencasts directory
		then continue		# leave downloads directory alone
	elif [ "$i" == "*.part" ]	# do not interfere with downloads currently in progress
		then continue		# do not interrupt downloads currently in progress
	fi
	if [ -a "$i" ] 
	then		# Accesible
		OLDNAME="$i"
		NEWNAME=$(echo "$i" | tr 'A-Z ' 'a-z_' | sed 's/"/_/g' | sed "s/'/_/g" | sed 's/`/_/g' | sed 's/(/_/g ; s/)/_/g ; s/_-_/-/g ; s/\.-\./-/g ; s/-_-/-/g ; s/-\.-/-/g ; s/^_//g ; s/__/_/g ; s/,/-/g ; s/!/./g ; s/\.\././g ; s/\.-/./g ; s/-_/-/g ; s/_-/-/g ; s/\[/_/g ; s/\]/_/g ; s/#/./g ; s/&/+/g ; s/^\.//g ; s/:/./g ; s/%20/_/g')
		if [ "$NEWNAME" == "" ]
		then NEWNAME="_"
		fi
		if [ "$OLDNAME" == "_" ]
		then NEWNAME="_"
		fi
		if [ "$NEWNAME" != "$OLDNAME" ]
		then
			let loop+=1		# we had to rename something and should loop because further tidy may be necessary
#			echo ""	1>&2		# a blank line between files renamed for easy reading
			if [ -d "$NEWNAME" ]
			then 								# we have at least one directory involved
				if [ -d "$OLDNAME" ]
				then							# rsync the directories
					rmdir "$OLDNAME"
					if [ $? -ne 0 ]
					then
						nice ionice -c 3 rsync -auvzH ./"$OLDNAME"/.??*	"$NEWNAME" && rm -rf ./"$OLDNAME"/.??* 2>/dev/null
						nice ionice -c 3 rsync -auvzH ./"$OLDNAME"/*	"$NEWNAME" && rm -rf ./"$OLDNAME"
					fi
				else							# file vs directory
					echo "In `pwd` I want to rename the file "$OLDNAME" over the "$NEWNAME" directory, which would be bad, so you fix it manually..."	1>&2
					echo "...it could be a collision of a directory and a symbolic link... in which case edit $0"	1>&2
					exit -2
				fi
			else								# good, no directory in the way
				if [ -f "$NEWNAME" ]					# do we already have a file by this name?
				then							# filename collision, let's update
					if [ "$NEWNAME" == "$(echo "$OLDNAME" | tr '[:upper:]' '[:lower:]')" ]
					then						# EEEK, name match requires special handling on NTFS
						mv		./"$OLDNAME"	./"$OLDNAME"-
						nice ionice -c 3 rsync -auvzH	./"$OLDNAME"-	"$NEWNAME" && rm -rf ./"$OLDNAME"-
					else						# Phew, no need to dance around Bill Gates steaming pile of excrement
						nice ionice -c 3 rsync -auvzH	./"$OLDNAME"	"$NEWNAME" && rm -rf ./"$OLDNAME"
					fi
				else
					mv -v -- ./"$OLDNAME" "$NEWNAME"		# no filename collision, just rename
				fi
			fi
		fi

		# Directories are recursed if we were invoked with option -r
		if [ $recurse = 0 ]
			then	# we were commanded to recurse
				if [ -d "$NEWNAME" ] 
				then	# directory
					"$0" -r "$NEWNAME"	# Follow the White Rabbit, Neo.
				fi
		fi

# This code stupidly preserves the (possibly) older version rather then overwriting it with (possibly) newer version
# We would prefer to rsync the new toxic name over the old detoxed name (doing what detox itself SHOULD do (at least optionally))
# followed by removal of the toxic name, but we're skipping the rsync step now,
# But it will take some thought to implement the improved version doing so...
# Even better but even more work, would be to write a shell script wrapper around detox itself to fix it,
# as I'm not about to go hack detox code in another language.
#		for f in $(/usr/bin/detox ${NEWNAME} |& grep "Cannot rename" | sed 's/^Cannot rename //;s/ .*$//'|sort -u)
#		do
#		    if [ -f "${f}" ]
#		    then
#			echo "DETOXifying	 ${f}" 1>&2
#			rm			"${f}" 1>&2
#		    fi
#		done

	fi
    done	# i
done	# loop until no changes necessary
exit 0	# Pau!

