#!/bin/sh

#Copyright Alberto Bursi <alberto.bursi@outlook.it>
#This script is released under GPLv3 license.

#see description in printf text below

version="0.5"

#possible actions are "sync", "verify", "scrub" (which is a verify+repair), "cleanup" (deletes all parity files recursively).
action="$1"

# this script expects a path you want to work in, for example "/home", but will work fine with a single file too.
chosen_folder="$2"

#path where the script will drop the parity files, if not specified it will drop them in the same folder as the data files
parity_folder="$3"

#performance-related configuration useful if you add options above to increase CPU (and I/O) usage, these ensure you don't overload the system
#and freeze the user interface.
#these commands will give this script and par2 processes the least possible priority as this should NOT interfere with normal system operation
#uncomment them to enable (make sure you actually have renice and ionice installed in the right path, of course)
#/usr/bin/renice -n 20 $$
#/usr/bin/ionice -c2 -n7 -p$$

#checking that all commands are available

for i in grep find rm par2 printf stat dirname ; do

which $i > /dev/null
	if [ "$?" != "0" ] ; then
		echo Warning! $i not found, please install $i
		exit 1
	fi
done

generic_error_function(){
	#if the above command exited with an error status the script terminates
	if [ "$?" != "0" ] ; then
		echo Warning! error detected, terminating batch processing.
		exit 1
	fi
}

#if the action isn't within the list of accepted actions, print help message

if  [ "$action" != "sync" ] &&  [ "$action" != "verify" ] && [ "$action" != "scrub" ] && [ "$action" != "cleanup" ] ; then

		printf "
Copyright Alberto Bursi <alberto.bursi@outlook.it>
This script is released under GPLv3 license.

VERSION $version

this is a frontend for par2 command aka par2cmdline,
a tool that checksums and creates parity to recover corruption in files.
this frontend gives it an interface more suited for protecting data
in storage drives, as the par2 command itself was designed for protecting files
you upload to some sharing service, so it works like an archiver tool.

Since this is the 21st century, this script will work with folder
and file names with spaces too.

SYNTAX
[options=\"\"] par2drive action path [parity path]

ACTION:
sync (updates parity files or creates parity files if not found, uses 'par2 create' )
verify (checks integrity of files with given parity files, uses 'par2 verify')
scrub (does a verify and repairs files it can repair, uses 'par2 verify' and 'par2 repair')
cleanup (deletes all parity files of a single file or recursively).

PATH:
this script expects a path you want to work in, for example /home, but will work fine with a single file too.
If the path or file name contains spaces, please encase it with \", for example \"/my folder\" \n

PARITY PATH:
Optional argument where you can set a path where the script will place the parity files. Useful if you don't want to 
clutter up your data folders with the parity files generated by par2.

OPTIONS
The script allows to specify par2 options (to increase default CPU utilization, 
recovery block percentage and such). 
They can be added as variable before calling par2drive, using the \"options\" variable.

This example will set par2 redundancy to 10 percent (default is 5)

options=\"-r10\" par2drive sync \"./Test Folder\"

EXAMPLES:
par2drive sync \"./Test Folder\"
options=\"-r10\" par2drive sync \"./Test Folder\"
par2drive sync \"./Test Folder/test file.txt\"
par2drive sync \"./Test Folder\" \"./Test Folder/parity\"
par2drive sync \"./Test Folder/test file.txt\" \"./Test Folder/parity\"
"
exit 1

fi

files_that_failed=""

#list all files recursively from the chosen folder, written as path to file, aka /home/username/blabla/file that we can use directly in our payload commands
#the script filters out par2cmdline parity files as we don't want to make parity of them too
#this script will handle also file and folder names with spaces, because this is the 21st century

find "$chosen_folder" -type f | grep -v ".par2" | while read file

#the following command is better if you have file names with weird non-printable characters in them
#find "$chosen_folder" -type f | grep -v ".par2" | while read -d $'\n' file

do
	echo working on "$file"
	
	#if there is a parity folder we filter out dots from the folder path to account for relative path.
    if [ "$parity_folder" = '' ] ; then
    
        file_parity="$file"
        
        else    
        #filtering out the dots used by relative path to make a name we can use in the folder creation phase, 
        file_parity="$parity_folder""$( echo "$file" | sed 's/^\(\.\)*//' )"
    fi
    
    #extracting the file folder so it can be passed as -B argument later
    file_folder="$(dirname "$file")"
    
    if [ "$file_folder" = '.' ]; then 
        file_folder='./'    
    fi

    
	case $action in
		sync)       
            #checking last modfication time of files, if the time of the parity is higher than the time of the file
			#the file was not modified after the last time it was processed, and we skip it
			date_last_modified_file=$(stat -c "%Y" "$file")
			#echo last modified $date_last_modified_file
			#if there is no parity file then we write 0 so the next if clause can work
			if [ -f "$file_parity".par2 ] ; then
				date_last_modified_parity=$(stat -c "%Y" "$file_parity".par2)
			else
				date_last_modified_parity=0
			fi
			#echo parity last modified $date_last_modified_parity

			if [ "$date_last_modified_parity" -lt "$date_last_modified_file" ] ; then
                echo was modified, generating again the parity files
				#deleting current parity files
				rm -f "$file_parity".vol*.par2
				rm -f "$file_parity".par2
				#creating new parity files	
				par2 create $options -a"$file_parity".par2 -B"$file_folder" "$file"
				generic_error_function
            else
                echo was not modified, skipping
			fi
		;;

		verify)
			par2 verify $options -B"$file_folder" "$file_parity".par2 "$file"
			
			if [ "$?" != "0" ] ; then
				files_that_failed="$files_that_failed $file"
			fi
		;;

		scrub)
			par2 verify $options -B"$file_folder" "$file_parity".par2 "$file"
			#if verification failed, we repair the file, otherwise we skip it
			if [ "$?" != "0" ] ; then
				par2 repair $options -B"$file_folder" "$file_parity".par2 "$file"
				#if repair fails for some reason, we add it to the list.
				if [ "$?" != "0" ] ; then
				files_that_failed="$files_that_failed $file"
				else
				#if it went well, we delete the backup file
				rm "$file".1
                fi
                
			fi
		;;

		cleanup)
		rm -f "$file_parity".par2
		rm -f "$file_parity".vol*.par2
		;;

	esac

	if ( [ "$action" = "verify" ] || [ "$action" = "scrub" ] ) && [ "$files_that_failed" != "" ] ; then
		echo "####################"
		echo "####################"
		echo files that failed $action were:
		printf "$files_that_failed \n"
	fi


done
