#!/bin/bash
set -e
#
#	Checksum for Linux					v0.7.5b							License: GPLv2+
#
#	Welcome to Checksum for Linux - drop-dead simple hashing from your desktop!
#
# 	This is the BASH script part of Checksum for Linux, a simple checksum-like hash creation
# 	& verification package, aiming to recreate the basic point-and-[right-]click hashing
# 	functionality of the original checksum, except in the KDE Desktop environment*.
#
# 	Essentially, it's a handy right-click+GUI front-end for the ever-robust on-board *nix
# 	hashing tools, with a good few shiny knobs on; Features..
#
#	Hash or verify files and folders, even entire volumes, with a click..
#
#		Select "Checksum" from any folder's services menu, and Checksum springs into action,
#		creating MD5 or SHA-* hashes for all files in the folder, and all the folders inside,
#		and all the folders inside that, and so on, aka. "recursively", through the entire
#		directory tree.
#
# 		Select "Verfiy this folder", from your service menu, and Checksum searches the
# 		*entire* directory tree for .md5, .sha*, and .hash files, and verifies them *all*.
#
#	Full Desktop GUI integration.
#
#		The terminal is not required. Create and verify hashes directly from your desktop,
#		using Dolphin/Konqueror/etc. service menus. For output, Checksum supports both
#		kdialog (KDE) and zenity (Gnome) dialogs and notifications. Or you can use the shell.
#
#	Configurable File Masks..
#
#		Checksum only music files, or whatever; with both single and multiple file masks.
#
#	MD5/SHA1/SHA2 hashing algorithms supported.
#
#		Okay, and SHA-384 and SHA-512, because it was so trivial to tag on.
#
#	Intellignet append.
#
#		Added a few files to a folder? No problem. With intelligent append, you can simply
#		checksum again, and *only* the new hashes get added. It's all automatic!
#
#	Multihashing_(TM), with generic .hash file handling..
#
#	  As well as full .md5 and .sha1 support, of course!
#
#		So if you upgrade algorithms later, or wish to include multiple hashing algorithms
#		within a single hash file; no problem; checksum can verify them all. As well as
#		inferring security benefits, this enables the use of a cute, generic ".hash"
#		extension for *all* your checksums! (the real reason) One file fits all, baby!
#
# 		Checksum also makes it trivial to create such a file, with intelligent hash append;
#		auto-matically skipping any files already hashed with your current algorithm. Neat.
#
#	Read-only Fall-back
#
#		Checksum can automatically switch to a designated fall-back folder in the event
#		of encountering read-only conditions in the checksumming location.
#
#	Shell use (mostly) supported.
#
#		You want these groovy features in your shell, go for it!
#
#
# Usage:
#
# 	In the shell..
#
#		To create hashes:
#
#			checksum <file|folder>
#
#		To verify hashes:
#
#			verify <checksum file|folder>
#
# 	Of course, the whole point of this, is to have the power of these commands on your
# 	desktop, in the form of some handy services. The accompanying .desktop files enable
# 	you to do exactly this. These simply launch checksum with different switches, instructing
#	Chec ksum to post GUI dialogs, notifications and such in your desktop environment.
#
# 	There is a choice of kdialog (KDE) and zenity (Gnome) GUI reporting, and checksum will
# 	use whatever you specify on the command-line, or with symlinks. You can also force zenity
# 	dialogson KDE, if you prefer. zenity's dialogs and notifications may be superior, but you
# 	lose the ability to "Do not show this message again". FYI; The preferences for those
#	"kdialogs" are in ~/.kde/share/config/checksum.
#
#	Because both systems have advantages (kdialog also has a handy find command, for searching
#	the text dialogs. Pretty neat. But zenity text boxes scroll the output automatically, for
#	example), there is also a "hybrid" setting, where you can use the best bits from each program.
#
#
#	Command-Line Switches..
#
# 	You can override Checksum's behaviour with the use of command line arguments
# 	aka. "switches". These override *everything*, so, for example, if you run Checksum
# 	via the "kchecksum-sha1" symlink, and add the "--md5" switch, you get MD5 hashes.
# 	These also enable you to use checksum without *any* symlinks, if you prefer that.
#
# 	Available switches..
#
#			--verify			force verification mode, regardless of how checksum was called.
#			--kde				use kdialogs (handy, when not using a k* symlink)
# 			--zenity | --zen	force zenity dialogs
#			--hybrid			use hybrid kdialog + zenity GUI system (best of both)
# 			--xl				extensionless hashing (when not default - why oh why!)
# 			--append			set append to true when it is not (again, why?)
# 			--noappend			set append to false
# 			--algo				set hashext="algo", override generic .hash file creation.
# 			--md5 				default algorithm = MD5
# 			--sha | --sha1		default algorithm = SHA-1
# 			--sha2				default algorithm = SHA-256
# 			--sha3				default algorithm = SHA-384
# 			--sha5				default algorithm = SHA-512
#			--mask=file.mask	use regular file globbing patters.
#								e.g.. kchecksum --mask=*.avi /Archive/Movies
#								Multiple masks are okay, in the form.. --mask=*.mp3,*.ogg,*.flac
#
#	NOTE: Switches are applied in the above order, later switches overriding earlier switches.
#
# 	You can also run kchecksum, kverify, etc. (the GUI versions) from the terminal;
# 	handy if you need to debug/troubleshoot some [k|g]checksum/[k|g]verify operation;
# 	the normal output will appear in your terminal. Also see Logging, below.
#
#
# 	Checksum for Linux also comes with a handy Windows Hash File Converter script you can
# 	use to convert any hash files made with windows tools. It does nothing more clever
# 	than switch the linebreaks from DOS to UNIX, but hey, you *can* convert an entire
# 	volume of hash files with one click! See: convert-winhashes for more details.
#
# 	If you have any questions, mail me, or better yet, post them on the Checksum page..
#
# 	http://corz.org/linux/software/checksum/
#
# 	Have fun!
#
# 	;o)
# 	Cor < linux at corz d.t org >
#
#
# 	NOTE: 	Other than my own intense desktop usage, this has had *very* little testing;
#			so all comments, bug reports, etc., will be gratefully received.
#
#	ALSO:	Apologies for all the tabs and spaces in the kdialog titles, but I cannot
#			live with " - kDialog" slapped on there. If this interferes with any desktop
#			automations or somesuch you have running; edit them out. And let me know!
#
# 	*
# 	It should work fine with other Linux desktops, too, but the GUI & activation methods
# 	would need to be tweaked some. Gnome (zenity) dialogs are already coded in, though I
# 	currently have no Gnome desktop to figure out Nautilus' service menus. Real soon, now!
#
# 	**
# 	File systems have "directories", but on a desktop they are called "folders". ;o)


## NOTE{S}:
#

## KDE & Kdialog
#
#	The funky dbus stuff, this requires KDE 4 to function.
#

## Logging..
#
#	When you use checksum from inside your desktop, a logfile is created in
#	/tmp/checksum.log, which contains all the script output (stuff that you might see
#	if you used checksum from the shell). This file is (usually) deleted at boot time,
#	and can be handy meantime. For example, if you added debug echo statements into this
#	script, they could be viewed there. Or if nothing happened, that file may tell you why.
#
#	However, if you prefer, you can disable this logging altogether with a single
#	alteration to the checksum .desktop files - full details inside "checksum.desktop".
#
## ##

## SHA-* support..
#
#	Checksum's default operation is to create and verify MD5 hashes. Unless you have
#	specific security needs, MD5 is perfect for file verification purposes, and is
#	also faster, in use, than any of the SHA-* family of algorithms.
#
#	Having said that, Checksum fully supports both SHA-1 and SHA-256 hashing algorithms.***
#	There is a preference (below) where you can set your preferred default.
#
#	Also, if you use a symlink with a name ending 'sha1', checksum will magically switch
#	to use sha1sum for all its operations. Use "sha2" to get SHA-256; 'kchecksum-sha1',
#	'kchecksum-sha2', etc. Same story in a shell; 'checksum-sha1', etc. You can also use
#	the command-line switches described above. Or a combination of both. Your call.
#
#	Checksum comes with a pre-made selection of .desktop files facilitating easy hashing
#	with the SHA-* hashing algorithms, which most people probably won't need.
#
#	NOTE: If you set your default algorithm to SHA-something, you can override the other
#	way, too, by using a symlink ending in "md5", or by use of the '--md5' switch,
#
#	During *verification*, checksum automatically switches algorithms, depending on
#	the current hash file extension, md5 => MD5; sha1 => SHA-1; sha2 => SHA-256; etc.,
#	so you don't have to set anything special to verify them. One command verifies ALL!
#
#	If you are using a generic extension, e.g. ".hash", checksum will interrogate the
#	file on a line-by-line basis, interrogating the individual hashes, and using
#	whatever tool is most approprite for each individual hash.
#
#	FYI; if you ask checksum to verify a file of a *completely* unknown extension, it
#	will be checked with md5sum, just in case it really is an old checksum file. It
#	will *not* be treated as a generic .hash file. This is by design.
#
#	*** Checksum also supports SHA-384 ('sha3') and SHA-512 ('sha5') hashing.
#		But let's be serious - this is file verification!
#
## ##


## :BEGIN PREFS
#

# Default Hashing Algorithm..	[string]		[default: algorithm='md5']
#
# Choose from 'md5' (for MD5), 'sha1' (SHA-1), or 'sha2' (SHA-256).
#
algorithm='md5'
#
# NOTE: you can override this default algorithm at any time by accessing checksum
# via a symlink of the appropriate name; e.g. 'kchecksum-sha1' for SHA-1 hashing.
# See the notes above, for more details. There's also 'sha3' and 'sha5', for nutters!


# Hashfile extension.		[string {file extension}]		[default: hashext='hash']
#
# Your preferred hash extension (for creating hashes)..
# files of this extension will also be treated as checksum files during verification.
#
hashext='hash'
#
# Checksum fully supports MultiHashing, and will happily verify a mix of MD5 and SHA
# hashes, even mixed up in the same file, hence the ability to use a single, generic
# .hash extension for *all* your hashes, as well as some other goodies.
#
# If you use your /own/ generic extension, you will probably want to also add it to your
# system filetype associations, so it automatically runs with kverify %f, or whatever.
#
# (at the time of writing, checksum doesn't setup .hash files, either; this is planned)
#
# There is also the special value, 'algo', which will use the currrent algorithm as the
# hash extension; e.g. an MD5 checksum file might be called, 'foobar.md5'.  While
# clutterish and less flexible, if you tend to hash very large numbers of small files;
# note; this setting has performance benefits during verification, because there is no
# need to interrogate the hash file line-by-line, or switch hashing tools: the
# extension sets the algorithm. You can also set this at runtime, with switches, so it
# may still be a good idea to leave the generic extension, and specify --algo as needed.
#
# ** IMPORTANT **
#
# DO NOT set this to 'md5', or some other algorithm - use 'algo', and set your default
# algorithm, above, instead.
#


# Append hashes to existing hash files?			[bool {0|1}]	[default: append=1][true]
#
# Checksum can automatically append any hashes you create, onto existing hash files.
# As well as making it trivial to add new files to an existing folder hash, this enables
# you to easily make MultiHashes, adding SHA-1 hashes into an existing MD5 hash file, or
# some other combination of actions. The possibilities are vast. You could easily create
# a hash file that was *ordered*; movies, music, archives, everything else, for example.
#
# Otherwise, set this to 0, and Checksum will ask you if you wish to overwrite existing
# hash files as and when it discovers them. Boring!
#
append=1


# Extensionless Hashes?		[bool {0|1}]			[default: extensionless=1][true]
#
# When hashing individual files, should checksum create 'extensionless' hash files?
# e.g. MyMovie.avi >> 'MyMovie.md5', NOT 'MyMovie.avi.md5' .
#
extensionless=1
#
# As well as being rather elegant, it's rather efficient; a hash for 'foo.avi' can
# be stored alongside hashes for 'foo.mpg', 'foo.html', & 'foo.nfo', etc., in one
# file. During verification, it looks just like a regular folder checksum. wekl!!


# Force Zenity?				[bool {0|1}]				[default: force_zenity=0][false]
#
# If you prefer to use zenity's notifications and dialogs, even when kdialog is available
# on KDE, set this to 1.. NOTE: You can also force zenity by using any of the g* symlinks.
#
force_zenity=0
#
# Don't forget to install zenity!	(sudo aptitude install zenity)


# Hybrid GUI System
#
# This basically uses kdialog where it might be useful to have the "do not show this again"
# option, and zenity for everything else. The advantage of this is three-fold; firstly, you
# get the "Do not show this again" option for dialogs where such things might matter. Secondly,
# you get superior notifications which stay put throughout the length of the entire operation,
# and thirdly, you get a neat icon in your system tray, as opposed to the rather distressing
# kNotifications! Zenity also scrolls its text output to the end (where the errors are), which
# is nice. This was trivial to implement, and trivial for you to customize, if required. See below.
#
hybrid=1
#
# Obviously, for this to work as expected, you need both kdialog and zenity installed.


# Ignore certain file types		[array]
# 								[default: ignore_extensions=('desktop' 'ini' 'lnk' 'directory')]
#
# When creating hashes, checksum will skip files with the following extensions..
# DO Leave a space between each quoted entry. No dots required (unless the extension has one)
#

ignore_extensions=('desktop' 'ini' 'lnk' 'directory' 'nfo' 'm3u' 'pls' 'url' 'sfv')
#
# NOTE:	The 'directory' entry is for '.directory' files, NOT directories!
# NOTE:	Checksum will also automatically skip any hash files.


# Fall-Back location		[folder path]			[default: fallback_dir="~/Hashes"]
#
# If you ask checksum to create hashes of some read-only file/directory/volume, it
# obviously cannot store the hashes in the usual location, and so will use a fall-back.
#
fallback_dir="$HOME/Hashes"
#
# You can use "$HOME" variable to refer to your actual home folder.
# NOTE: You must use double-quotes to expand $VARIABLES. ("$var" not '$var')


# Temp file					[file path]			[default: tmpfile='/tmp/checksum.tmp']
#
# Temporary file for folder verification (usually inside /tmp/). Output from multiple
# checksum files is collected here and posted at the finish. This gets wiped on every run.
#
tmpfile='/tmp/checksum.tmp'
#
# NOTE: The command-line inside any .desktop files used to launch Checksum may also log.
# By default, a semi-permanent (deleted@boot) log will be created in /tmp/checksum.log

# Error Log					[file path]		[default: errlog='/tmp/checksum-errors.log']
#
# Another temporary file, wiped at run-time. Any verification errors are collected
# here and its output is posted at the foot of your verification results.
#
errlog='/tmp/checksum-errors.log'


# Keep the error log?				[bool {0|1}]			[default: append=0][false]
#
# Set this to 1, and the error log (if there is one) will be time-stamped and copied over
# to the working directory at the end of the verification procedure. In actual fact, you
# get a log of the entire operation + the error log joined together.
#
keep_errlog=0


# Dialog Maximum Height
#
# If you have an especially large monitor, you may wish to hack a higher value here..
#
dh_max=600


# Show Times.
#
# If you are testing or something, you might want to enable this. Total time is reported.
# NOTE: this time is sent to stdout, so you will need to either examine checksum.log
# (or whatever it's called in your .desktop files), or else run things from the shell.
show_times=1

#
## :END PREFS


# Note to experienced BASH coders..
#
# I'm an amateur bash coder, but I'd like to be better.
# All suggestions for improvements to Checksum will be warmly received.


# init..
mode=
mask=
folder=0
filebad=0
notifypid=0
existing=()
# simple flag set if we use fall-back
fb=0

# this changes, depending somewhat on the dialog's content..
diag_height=600

# this may change, depending on the symlink used..
me="${0##*/}"

# use last parameter (we may add --switches, later)..
path="${!#}"
# that changes, so we'll make a copy, now..
opath="$path"

# I do hope we won't need this..
here="$(pwd)"

# setup..
rm -rf "$errlog"
rm -rf "$tmpfile"
# for testing..
start_time=$(date +%s)


# A few functions..
#

# SwitchAlgo	{string}
# Switch hashing algorithm by file extension..
#
# This sets the file extension and actual binary used to compute the hash(es)..
#
# If you wanted to hack the default algorithm used for *completely* unknown files
# (aka. fallback algorithm), this is where you could do it. Currently it's md5..
# Of course, it's better if you have the correct extensions on your hash files.
# And best of all to use a generic extension, e.g. "hash". ;o)
#
# Fall-back only comes into effect if you specifically ask checksum to verify a
# single, non-hash-extensioned file, e.g. "kverify foo.bar", where 'bar' is unknown.
#
# This function gets used a *lot*.
#
SwitchAlgo() {
	case "$1" in
		sha1)
			algo='sha1'
			tool='sha1sum'
			hashlen=40
			;;
		sha2)
			algo='sha2'
			tool='sha256sum'
			hashlen=64
			;;
		sha3)
			algo='sha3'
			tool='sha384sum'
			hashlen=96
			;;
		sha5)
			algo='sha5'
			tool='sha512sum'
			hashlen=128
			;;
		*)
			algo='md5'
			tool='md5sum'
			hashlen=32
			;;
	esac
}


# CheckMultiHash	{file path}
#
# This function accepts the local path to a checksum file,
# interrogates it on a line-by-line basis, and checks each
# recorded hash against a freshly computed hash, reporting
# success or failure, depending.
#
CheckMultiHash() {

	cat "$1" | while read line; do

		xhash=${line%% *}
		file=${line#*\*}

		# if it's not hash-looking, don't bother..
		if [[ $xhash =~ ^[[:xdigit:]]+$ ]]; then

			if [[ ! -f "$file" ]]; then
				echo "$file: $algo: MISSING"
				echo "$1: $algo: MISSING: $file" >> "$errlog"
				continue
			fi

			# calculate a nice new hash,,
			case ${#xhash} in
				32)
					SwitchAlgo 'md5'
					nnh=$($tool "$file")
					nnh=${nnh:0:32}
					;;
				40)
					SwitchAlgo 'sha1'
					nnh=$($tool "$file")
					nnh=${nnh:0:40}
					;;
				64)
					SwitchAlgo 'sha2'
					nnh=$($tool "$file")
					nnh=${nnh:0:64}
					;;
				96)
					SwitchAlgo 'sha3'
					nnh=$($tool "$file")
					nnh=${nnh:0:96}
					;;
				128)
					SwitchAlgo 'sha5'
					nnh=$($tool "$file")
					nnh=${nnh:0:128}
					;;
				*)
					# some other hex digit!
					continue
					;;
			esac

			if [[ "$nnh" = "$xhash" ]]; then
				echo "$file: $algo: OK"
			else
				echo "$file: $algo: FAILED"
				echo "$1: $algo: FAILED: $file" >> "$errlog"
			fi
		fi
	done | tee -a "$tmpfile"
}


# read the number of lines in an output file, and *roughly*
# set the dialog height to match. ish. Did I mention this isn't precise?
SetDialogHeight() {
	fl=$(wc -l < "$1" )
	diag_height=$((100+(fl*=30))) # magic!
	if [[ $diag_height -gt $dh_max ]] || [[ $diag_height -lt 50 ]]; then diag_height=$dh_max; fi
}


# add a checksum file's hashes into the existing() array
grabhashes() {
	while read; do
		hash=${REPLY%% *}
		[[ ${#hash} -eq $hashlen ]] && existing+=("${REPLY#*\*}")
	done < "$1"
}

# pop-up notification message, usually..
notify_user() {

	case $dlg_mode in

		0)
			echo
			echo "$2 hashes in \"$1\" .."
			echo "** To abort, press Ctrl-C **"
			echo
			;;
		1 | 3)
			zenity 	--notification --text="checksum: $3 in progress (click to hide)" \
										--window-icon="$HOME/.local/share/icons/checksum.png" &
			notifypid="$!"
			;;
		2)
			kdialog --title "Checksum: $3 in progress.." --passivepopup \
										"(click here to hide this notification)" 5
			;;
	esac
}


log_out() {
	if [[ -s "$errlog" ]]; then
		echo "" >> "$tmpfile"
		echo "THERE WERE ERRORS!" >> "$tmpfile"
		echo "" >> "$tmpfile"
		cat "$errlog" >> "$tmpfile"
	else
		echo "" >> "$tmpfile"
		echo "All checksums 100% AOK!" >> "$tmpfile"
	fi
}


TestDirWrite() {
	tname="$1.$(date +%s%N).test"
	echo "write***test" > "$tname"
	wtest=$(cat "$tname")
	rm -rf "$tname"
	if [[ $wtest = "write***test" ]]; then
		rodir=0
	else
		rodir=1
	fi
	return 0
} > /dev/null 2>&1 || true


# Setup user preferences..
#


# Dialog mode..
case "$me" in
	k*) # kchecksum, kverify, etc.
		# we will use kdialog..
		dlg_mode=2
		;;
	g*) # gchecksum, gverify, etc.
		# we will use zenity..
		dlg_mode=1
		;;
	*) # checksum, verify, etc.
		# no dialogs. output to stdout..
		dlg_mode=0
		;;
esac

##	hybrid mode => dlg_mode=3
## We simply add an " | 3" wherever we want to apply SuperFandangoHybridGUIElements.
## If you'd prefer *your* hybrid system to be different, simply switch those around.


# runtime [symlink]overrides..
#
case "$me" in
	# the first condition is for users who have sha* hashing set as the default, but wish
	# to override by the use of an MD5-specific symlink, e.g. "kchecksum-md5 movie.avi"
	 *md5) algorithm="md5" ;;
	*sha1) algorithm="sha1" ;;
	*sha2) algorithm="sha2" ;;
	*sha3) algorithm="sha3" ;;
	*sha5) algorithm="sha5" ;;
esac


# grab any command-line switches..
#
while [[ $# -gt 0 ]]; do
    case "$1" in
        --kde)				dlg_mode=2 ;;
        --zenity | --zen)	force_zenity=1 ;;
		--hybrid)			hybrid=1 ;;
        --xl)				extensionless=1 ;;
        --append)			append=1 ;;
        --noappend)			append=0 ;;
        --algo)				hashext="algo" ;;
		--md5) 				algorithm="md5" ;;
		--sha | --sha1)		algorithm="sha1" ;;
		--sha2)				algorithm="sha2" ;;
		--sha3)				algorithm="sha3" ;;
		--sha5)				algorithm="sha5" ;;
		--mask*)			mask="$1" ;;
    esac
    shift
done

## NOW we can set the user's default Hashing Algorithm....
SwitchAlgo "$algorithm"

# During folder/tree verification, we shall consider these types to be hash files..
# it's down here so that "average" users don't mess with it.
#
hash_types=([0]="md5" [1]="sha1" [2]="sha2" [3]="sha3" [4]="sha5")
#
# NOTE: if the number of entries in this list gets alterered, you must also alter the
# find command which uses it, below - simply search this script for "{hash_types[0]}".


# just in case they can't read..
for i in "${hash_types[@]}"; do
	if [[ $i = "$hashext" ]]; then
		# No! I will not set it to $algo! Yet..
		hashext="algo"
		break
	fi
done

# special extension setting:
# match checksum file extension to algorithm used (alternative to generic extension)..
[[ "$hashext" = "algo" ]] && hashext="$algo"


# Ignore types..
# An array of file types (by extension) to skip when creating hash files.
# we simply concatenate the hash types array with the user's ignore types array..
ignore_extensions=("$hashext" "${hash_types[@]}" "${ignore_extensions[@]}")



## GUI setup.
#

# basic checks for hybrid gui mode
# (but not if called from the shell with no k|g* symlink)..
if [[ $hybrid -eq 1 ]] && [[ $dlg_mode -ne 0 ]]; then
	dlg_mode=3;
	[[ ! -x /usr/bin/zenity ]] && dlg_mode=2;
	[[ ! -x /usr/bin/kdialog ]] && dlg_mode=1;
fi

# force zenity dialogs..
[[ $force_zenity -eq 1 ]] && [[ $dlg_mode -eq 2 ]] && dlg_mode=1

# kdialog is missing, fall-back to zenity..
[[ ! -x /usr/bin/kdialog ]] && [[ $dlg_mode -eq 2 ]] && dlg_mode=1

# zenity is missing, fall-back to kdialog. If kdialog is missing, fall-back to stdout..
if [[ ! -x /usr/bin/zenity ]] && [[ $dlg_mode -eq 1 ]]; then
	if [[ -x /usr/bin/kdialog ]]; then
		dlg_mode=2
	else
		dlg_mode=0
		echo "***	NOTE	***"
		echo "Checksum can use zentiy to display dialogs in your desktop. "
		echo "To install zenity (on a debian-based system, like Ubuntu) do:"
		echo
		echo " sudo apt-get install zenity"
		echo
		echo "For other systems, check your package manager's man page, or"
		echo "compile from source. See: http://freshmeat.net/projects/zenity"
		echo
		#exit 7
	fi
fi


# are we creating or verifying?
case "$me" in
	checksum* | kchecksum* | gchecksum*)
		mode="create"
		usg_str=""
		;;
	verify* | kverify* | gverify*)
		mode="verify"
		usg_str="Hash "
		;;
esac

[[ "$mode" = '' ]] && exit 2

# more user error!..
if [[ "$path" = "$0" ]] || [[ ! -a "$path" ]]; then
	msg='*** NO PATH WAS SPECIFIED! ***'
	gmsg='Something bad happened. Please reinstall checksum!'
	if [[ "$path" = "$0" ]]; then
		msg='*** NO SUCH PATH! ***'
		gmsg='Checksum got sent a non-existant path!!!'
	fi
	case $dlg_mode in

		0)
			echo
			echo "$msg"
			echo
			echo "Usage: To $mode hashes.."
			echo
			echo $me' <'$usg_str'File|Directory>'
			echo
			;;
		1 | 3)
			zenity	--error --title 'Error!!!' --text "$gmsg" \
				--width=280 --timeout=20 --window-icon="$HOME/.local/share/icons/checksum.png"
			;;
		2)
			kdialog --error "$gmsg" -title 'Checksum Error!	 	 	 	 	 	 	 	 	 	 	 	 	 	 	'
			;;
	esac
	exit 1
fi


# still here? Okay, let's look at that path..
filename=${path##*/}
fileext=${filename##*.}

# was it a folder?
[[ -d "$path" ]] && folder=1

# just in case..
[[ "$fileext" = "$filename" ]]&& fileext=""

# create a hash of a hash file!?!..
if [[ -f "$path" ]] && [[ "$mode" = "create" ]]; then
	# if it looks like a duck..
	#2do.. use the types array..  if (inarray(foo,bar)).. I wish!
	if [[ "$fileext" = "$algo" ]] || [[ "$fileext" = "$hashext" ]]; then
		hasherr='This is a checksum file. You can verify it.'
		case $dlg_mode in

			0)
				echo "$hasherr"
				;;
			1 | 3)
				zenity	--info --title "Hashes Of Hashes!" --text="$hasherr" --timeout=10 \
						--window-icon="$HOME/.local/share/icons/checksum.png"
				;;
			2)
				# kDialog likes to post " - kDialog" on every window title *grrr*
				kdialog --msgbox "$hasherr"  --title "Checksum WHAT!?!	 	 	 	 	 	 	 	 	 	 	 	"
				;;
		esac
		exit 11
	fi
fi



## Let's CREATE!
#



if [[ "$mode" = "create" ]]; then

	if [[ $folder -eq 1 ]]; then
		if [[ "${path%/*}" == "$path" ]]; then	# "local" shell usage.. cd foo && checksum bar
			hashfile="$filename.$hashext"
			# switch the "original" path, for any fall-back hashing..
			opath="$here/$path"
		else
			hashfile="$path/$filename.$hashext"
		fi
		cd "$path"
	else
		if [[ "${path%/*}" == "$path" ]]; then
			#2do.. read-only single hash file..
			path="./"
			#opath="$here/$path"
		fi
		if [[ $extensionless -eq 1 ]]; then
			hashfile="${path%/*}/${filename%\.*}.$hashext"
		else
			hashfile="${path%/*}/$filename.$hashext"
		fi
		cd "${path%/*}"
	fi


	# no hash file, cool.
	#
	if [[ ! -f "$hashfile" ]]; then
	
		# but is the dir writeable?..
		TestDirWrite $hashfile
		[[ $rodir -eq 1 ]] && fb="$hashfile"

	# Hash File EXISTS! ..
	#
	else
		# The hash file is not writeable..
		if [[ ! -w "$hashfile" ]]; then
			# they may be hashing IN the fallback location! So we use a flagoid.
			fb="$hashfile"
		else
			# the hash file is writeable,. 
			if [[ $append -eq 1 ]]; then
				# build an array ofexisting files + hashes..
				grabhashes "$hashfile"
			else
				# Do they wish to overwrite it?
				case $dlg_mode in

					0)
						until [[ "$answer" != "" ]]; do
							echo
							echo 'HASH FILE EXISTS!'
							echo 'Do you wish to overwrite it?'
							echo
							select answer in yes no; do
								[[ "$answer" != 'yes' ]] && exit 9
								break
							done
						done
						echo
						;;
					1)
						zanswer=$(zenity --question --title='Checksum File Exists!!!' --width=280 \
							--text="A checksum file already exists.\nShall I overwrite it?" \
									--timeout=10 --window-icon="$HOME/.local/share/icons/checksum.png")
						[[ "$zanswer" ]] && exit 9
						;;
					2 | 3)
						kdialog \
							--warningyesno "  A checksum file already exists.       \n  Shall I overwrite it?      " \
							--dontagain 'checksum:do_overwrite' \
							--title 'Checksum File Exists!!!                              '
						;;
				esac
			fi
		fi
	fi


	# using the fall-back location..
	if [[ "$fb" != 0 ]]; then
		[[ ! -d "$fallback_dir" ]] && mkdir "$fallback_dir"
		hashfile="$fallback_dir/${hashfile##*/}"
		# add more hashes to our existing() array..
		[[ -f "$hashfile" ]] && grabhashes "$hashfile"
	fi


	if [[ ! -f "$hashfile" ]] || [[ $append -eq 0 ]]; then
		# okay, let's start a new hash file..
		echo '# Checksum for Linux..' > "$hashfile"
		echo '# http://corz.org/linux/software/checksum/' >> "$hashfile"
		[[ "$fb" != 0 ]] && echo "# hashes continued from read-only checksum file @ $fb" >> "$hashfile"
	fi


	# One last check..
	if [[ ! -w "$hashfile" ]]; then
		echo 'I am having trouble creating the checksum file.'
		echo "Check the permission on your fallback location: $fallback_dir"
		exit 9
	fi


	# okay, we're good to go..
	notify_user "$hashfile" 'Creating' 'Hashing'

	if [[ $folder -eq 1 ]]; then
		# Kinda hackish, but fun..
		if [[ "$mask" != '' ]]; then
			fmask=
			mask="${mask##*=}"	# remove "--mask=" part
			until [[ "$mask" = ',' ]]; do
				tmask=$(echo "$mask" | cut -d\, -f1)
				fmask=" -iname '$tmask'$fmask"
				mask="${mask#*,}"
				# on the final pass we add a comma so the previous line removes the final mask next time around. ;o)
				[[ "$(echo "$mask" | cut -s -d\, -f1)" = "" ]] && mask="$mask,"
				[[ "$mask" != ',' ]] && fmask=" -o $fmask"
			done
		fi
		[[ "$fmask" != '' ]] && fmask="  \( $fmask \) "

		#2do.. investigate: add name exclusions to find??
		eval "find . -type f ${fmask} -print" | while read i; do

			# check for ignored file types..
			thisname="${i##*/}"
			thisext="${thisname##*.}"
			for j in "${ignore_extensions[@]}"; do
				if [[ $j = "$thisext" ]]; then
					[[ $dlg_mode -eq 0 ]] && echo "*** ** skipping IGNORED file ** *** ${i##*/}"
					continue 2
				fi
			done


			# if appending - check against algo array, and skip..
			if [[ $append -eq 1 ]]; then
				for xfile in "${existing[@]}"; do
					if [[ "$xfile" = "$i" ]]; then
					[[ $dlg_mode -eq 0 ]] && echo "*** ** skipping checked file ** *** ${i##*/}"
					continue 2
				fi
				done
			fi
			## NOTE: if using full paths in fall-back checksum file, ($i = relative path) #2do

			# fall-back has been set, we are in a new location - rewrite the hash paths?
			if [[ "$fb" != 0 ]]; then

				# this method will write FULL absolute paths into the fallback hash file..
				$tool -b "${opath}/${i#*/}"  2>>"$errlog" | tee -a "$hashfile"

# 				#and/or we could employ some kind of user policy..
# 				case $fb_method in	### ?
# 					0)
# 						;;
# 					1)
# 						;;
# 					*)
# 						;;
# 				esac


			else
				# regular write-access..
				$tool -b "${i}"  2>>"$errlog" | tee -a "$hashfile"
			fi
		done

	else
		# hash a single file..

		if [[ "$fb" != 0 ]]; then
			echo "fallback hash foo"
			$tool -b "${i}"  2>>"$errlog" | tee -a "$hashfile"
		else
			$tool -b "$filename"  2>>"$errlog" | tee -a "$hashfile"
		fi

		SetDialogHeight "$hashfile"
		#diag_height=150
	fi

	# this may be zenity, or may be hybrid, so put it first..
	[[ "$notifypid" -ne 0 ]] && kill "$notifypid"

	case $dlg_mode in

		0)
			echo
			echo 'All done with hashing.'
			echo
			;;
		1)
			zenity	--info --title 'All Done!' --text='Checksum operation complete.    ' --timeout=10 \
					--window-icon="$HOME/.local/share/icons/checksum.png"
			;;
		2 | 3)
			kdialog --msgbox ' Checksum operation complete!    ' \
				--title 'Checksum Complete!                                                                    ' \
				--dontagain 'checksum:notify_when_complete'
			;;
	esac




## VERIFY hashes..
#

else

	# Verify a folder..
	#

	if [[ $folder -eq 1 ]]; then

		notify_user "${path%/*}" 'Verifying' 'verification'

		cd "$path"
		find . -type f  \( -name "*.$hashext" -o -name "*.${hash_types[0]}" \
				-o -name "*.${hash_types[1]}" -o -name "*.${hash_types[2]}" \
				-o -name "*.${hash_types[3]}" -o -name "*.${hash_types[4]}" \) -print | while read i; do

				chkerror=0
				hashfname="${i##*/}"
				thishext="${hashfname##*.}"

				cd "$path/${i%/*}"
				echo "$hashfname: " >> "$tmpfile"

				if [[ "$thishext" = "$hashext" ]]; then
					CheckMultiHash "$hashfname"
				else
					SwitchAlgo "$thishext"
					cout=$($tool --check -- "$hashfname" 2>>"$errlog") || chkerror=1
					echo "$cout" | tee -a "$tmpfile"
				fi

				if [[ "$chkerror" == 1 ]]; then
					echo "IN: $i" >> "$errlog"
					echo "" >> "$errlog"
				fi

				echo "" >> "$tmpfile"
			done

		if [[ -s "$tmpfile" ]]; then
			echo "" >> "$tmpfile"
			log_out
			SetDialogHeight "$tmpfile"
		fi

		[[ "$notifypid" -ne 0 ]] && kill "$notifypid"

		case $dlg_mode in

			1 | 3)
				if [[ -s "$tmpfile" ]]; then
					zenity --text-info --editable --filename="$tmpfile" --title='Checksum Results..' \
							 --width=600 --height=$diag_height --window-icon="$HOME/.local/share/icons/checksum.png"
				else
					zenity	--info --title 'Nothing done!' --text='No checksum files were found!    ' --timeout=10 \
							--window-icon="$HOME/.local/share/icons/checksum.png"
				fi
				;;
			2)
				if [[ -s "$tmpfile" ]]; then
					kdialog --textbox "$tmpfile" 600 $diag_height	\
						--title 'Checksum Results.. 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	'
				else
					kdialog --sorry 'No checksum files were found!' \
						--title 'Nothing done! 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	'
				fi
				;;
		esac



	# Verify a hash file..
	#
	# While technically feasible to have some reusable function, I like to keep this
	# separate and deal with single files (and their notifications) slightly differently..
	#

	else

		cd "${path%/*}"
		hashfile="$path"
		notify_user "$filename" 'Verifying' 'verification'

		# a .hash file..
		if [[ "${hashfile##*.}" = "$hashext" ]]; then
			CheckMultiHash "$hashfile"
		else
			# oldschool .md5/.sha1 file...
			SwitchAlgo "${hashfile##*.}"
			if [[ "$dlg_mode" -eq 0 ]]; then
				$tool --check -- "$hashfile" 2>>"$errlog"
			else
				$tool --check -- "$hashfile" >> $tmpfile 2>>"$errlog" || echo "IN: $hashfile" >> "$errlog"

			fi
		fi

		log_out
		SetDialogHeight "$tmpfile"
		[[ "$notifypid" -ne 0 ]] && kill "$notifypid"

		case $dlg_mode in

			0)
				echo
				echo 'All done with verification!'
				echo
				;;
			1 | 3)
				zenity	--text-info --title='Checksum Results' --filename="$tmpfile" --width=600 \
						--height=$diag_height --window-icon="$HOME/.local/share/icons/checksum.png" \
								--text="Verifying hashes in \"$filename\". This may take a moment.."
				;;
			2)
				kdialog --textbox $tmpfile 600 $diag_height --title 'Checksum Results..	     '
				;;
		esac

	fi


	if [[ "$keep_errlog" -eq 1 ]] && [[ -s "$errlog" ]]; then
		cp "$tmpfile" "${path%/*}/checksum_errors [$(date)].log"
		cat "$errlog" >> "${path%/*}/checksum_errors [$(date)].log"
	fi

fi


if [[ "$show_times" = 1 ]]; then
	finish_time=$(date +%s)
	echo "All done in $((finish_time - start_time)) seconds."
fi
exit 0


##
#	Context/Service/Action/Right-Click Menus..
#
#	To get the right-click action, simply create some.desktop files inside..
#
#	/home/<USER NAME>/.kde/share/kde4/services/ServiceMenus
#

##	Example..
#
# You could roll them into one single "checksum" action, though I prefer
# to keep more than one, with an eye on future improvements.
#
## This one is for regular files..
#
##!/home/cor/.kde/share/kde4/services/ServiceMenus/checksum.desktop
#
# # Simple checksum-like right-click "action" for files
# # (c) cor <inquire at corz d.t org> 2009
# # License: GPL.v3
#
# [Desktop Entry]
# Actions=MD5Checksum
# Encoding=UTF-8
# # assuming the icon is located at /home/<USERNAME>/.local/share/icons/checksum.<image extension>
# Icon=checksum
# MimeType=application/octet-stream;*/*;
# ServiceTypes=KonqPopupMenu/Plugin,*/*
# Type=Service
# # checksum does its own startup notifications, no need for bouncing hashes..
# X-KDE-StartupNotify=false
# # comment out the next two lines to put this service into the "Actions" submenu.
# X-KDE-Priority=TopLevel
# # comment out just this next line to have the checksum command in the *main* menu.
# X-KDE-Submenu=Checksum
#
# [Desktop Action MD5Checksum]
# Name=Checksum (MD5)
# Icon=checksum
# #Exec=/bin/sh -c "kchecksum '%f'"
# # remove the logging if you like. It's deleted at every bootup, and is handy.
# Exec=/bin/sh -c "kchecksum '%f' >> /tmp/checksum.log"






## 2do..
#

# switch to $algodeep tools where applicable (*should* be faster)

# folder hashing with *individual* hashes (e.g. all files get inividual .hash)

# Silent operation (dlg_mode = -1)

# Notify when failures *may* have been caused by encountering a Windows checksum file
# (is there a CRLF in the file?) & perhaps an option to automatically convert those files on-the-fly.

# gui notification on errors? Beep, maybe?

# beep when done - handy when completion dialog has been disabled.

## Gnome desktop activation. I must install Gnome and see how it works there!

# multihash-in-one-go - user could have an array of chosen algos, e.g. multi=( "md5" "sha1" )
# when not multihashing, simply set it to $algo

# when user attempts to hash a single hash file - rather than redundant message (literally!)
# post some random easter egg, like "The tops of old socks make excellent hair-bands." <OK>
# maybe put the info too, in brackets.

# per-folder checksum files? Hmmm. Maybe.

# switch to bring up mini-options - basically file masks - maybe a presets list, though-[

# --choose flag, to bring up a dialog to choose file masks.
#	[default to "*.*"s perhaps, for clarity]

# hmmm.. would it be possible to pause and continue an operation?
#	or even continue a crashed (volume) verify and pick up from where we left off
#	(if the log wasn't in /tmp maybe. hmm)


## BUGS & Foibles..
#

# "&" in the file name will mess up verify results dialogs (needs to be converted to &amp;)
# I assume other entities will need to be treated the same way. When this happens, a generic
# message is posted instead, and checksum otherwise acts as normal, so it's no biggie.

