#!/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 }