#!/bin/bash

# Copyright © 2012-2026 ScriptFanix
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# A copy of the GNU General Public License v3 is includded in the LICENSE file
# at the root of the project.

copyFiles_action() {
	(( cron )) || echo -n $'Copying files...\033[K'
	# Query all destination_files whose last_change doesn't match the
	# source, restricted to mime_type_actions with action=2 (direct copy,
	# not transcode).
	# The join on mime_type_actions ensures we only copy files destined for
	# a destination that has explicitly mapped this MIME type to action=2.
	echo '
		SELECT
			source_files.filename,
			source_files.last_change,
			destinations.id,
			destinations.name,
			destination_files.id
		FROM			source_files
			INNER JOIN	destination_files
				ON	source_files.id
						= destination_files.source_file_id
			INNER JOIN	destinations
				ON	destination_files.destination_id=destinations.id
			INNER JOIN	mime_type_actions
				ON	mime_type_actions.id = source_files.mime_type
		WHERE	CAST(destination_files.last_change AS TEXT)
				<> CAST(source_files.last_change AS TEXT)
		AND	mime_type_actions.destination_id = destinations.id
		AND	mime_type_actions.action = 2;

		SELECT "AtOM:NoMoreFiles";' >&3
	# Results are NUL-delimited rows; sentinel value signals end of result
	# set.
	read -u4 -r -d $'\0' line
	while ! [[ $line = AtOM:NoMoreFiles ]]
	do
		copyfiles+=("$line")
		read -u4 -r -d $'\0' line
	done

	# Wrap all DB updates in a single transaction for performance.
	echo 'BEGIN TRANSACTION;' >&3
	for copyfile in "${copyfiles[@]}"
	do
		# Each row is a single string with columns joined by
		# ::AtOM:SQL:Sep::.
		# Strip prefix to extract each field in order, advancing $rest
		# each time.
		sourcefilename=${copyfile%%::AtOM:SQL:Sep::*}
		sourcedir=${sourcefilename%/*}
		rest="${copyfile#*::AtOM:SQL:Sep::}::AtOM:SQL:Sep::"
		lastchange=${rest%%::AtOM:SQL:Sep::*}
		rest=${rest#*::AtOM:SQL:Sep::}
		destinationid=${rest%%::AtOM:SQL:Sep::*}
		rest=${rest#*::AtOM:SQL:Sep::}
		destination=${rest%%::AtOM:SQL:Sep::*}
		rest=${rest#*::AtOM:SQL:Sep::}
		destfileid=${rest%%::AtOM:SQL:Sep::*}
		rest=${rest#*::AtOM:SQL:Sep::}
		(( count++ ))
		(( cron )) || printf '\b\b\b\b%3i%%' $(( (count * 100) / ${#copyfiles[@]} ))
		# If this destination uses a rename/path pattern, delegate path
		# resolution to guessPath(), which looks up the
		# already-transcoded sibling to find the correct output
		# directory.
		if [ -n "${destinationrenamepath["$destination"]}" ]
		then
			destdir="$(guessPath)"
			guessstatus=$?
			case $guessstatus in
				1)
					# guessPath found no transcoded
					# sibling; skip this file entirely.
					continue
				;;
				2)
					# Transcoded siblings exist but are not
					# yet up to date; defer this copy until
					# the next run so the directory is
					# stable.
					(( postponed++ ))
					continue
				;;
			esac
		else
			# No rename pattern: mirror the source directory
			# structure under the destination root, sanitizing each
			# path component for the target FS.
			destdir="${destinationpath["$destination"]}/"
			if [[ $sourcefilename =~ / ]]
			then
				destdir+=$(
					sanitizeFile "${sourcefilename%%/*}" dir
				)
				part=${sourcefilename#*/}
				while [[ $part =~ / ]]
				do
					destdir+="/$(
						sanitizeFile "${part%%/*}" dir
					)"
					part=${part#*/}
				done
				if ! [ -d "$destdir" ]
				then
					mkdir -p "$destdir"
				fi
			fi
		fi
		# Try the cheapest copy methods first: reflink (CoW, same
		# filesystem), then hardlink (csame filesystem), falling back
		# to a full data copy.
		if cp -a --reflink=always		\
			"$sourcepath/$sourcefilename"	\
			"$destdir"			\
			2>/dev/null			\
		|| cp -al				\
			"$sourcepath/$sourcefilename"	\
			"$destdir"			\
			2>/dev/null			\
		|| cp -a				\
			"$sourcepath/$sourcefilename"	\
			"$destdir"
		then
			# Newlines in filenames are stored as a safe inline
			# placeholder so the value can be embedded in SQL
			# without breaking row parsing.
			destfilename=${sourcefilename//$'\n'/::AtOM:NewLine:SQL:Inline::}
			Update destination_files			\
				filename				\
					"$destdir/${destfilename##*/}"\
				rename_pattern 				\
					"${destinationrenamepath[$destination]}/${destinationrename[$destination]}"\
				fat32compat				\
					${destinationfat32compat["$destination"]}\
				ascii					\
					${destinationascii["$destination"]}\
				last_change				\
					$lastchange			\
			 <<-EOWhere
				id = $destfileid
			EOWhere
			(( done++ ))
		fi
	done
	echo 'COMMIT;' >&3
	if (( count ))
	then
		(( cron )) || echo -n $'\r'
		echo -n "Copied ${done:-0} of $count"		\
		"files${postponed+ ($postponed postponed)}."
		(( cron )) || echo -en "\033[K"
		echo
	else
		(( cron )) || echo -n $'\r\033[K'
	fi
	unset count done
}
