#!/bin/bash
#                       /usr/local/bin/name_tidy
# https://crystalfaeries.net/posix/bin/name_tidy
# celeste:crystalfaery NAME_TIDY 2020-08-26 19:31:51+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

# 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

let recurse=1			# false by default
if [ "${1}" ]			# any arguments on command line?
then
	if [ "${1}" == "-r" ]	# yes, "-r" tells us to recurse subdirectories
	then
		let recurse=0	# true  by option
		shift		# finished option
	fi
fi
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

#	does this directory contain a "run control" file denying our tidy operation here?
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	0	# gracefully decline to work here
	else
		echo	"	tidying $directory"					1>&2
	fi
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!

