diff --git a/lib/copy/action b/lib/copy/action index b49aea7..c0a90ba 100644 --- a/lib/copy/action +++ b/lib/copy/action @@ -1,6 +1,25 @@ #!/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, @@ -22,6 +41,8 @@ copyFiles_action() { 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 @@ -29,9 +50,14 @@ copyFiles_action() { 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::" @@ -45,20 +71,33 @@ copyFiles_action() { 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 @@ -79,6 +118,9 @@ copyFiles_action() { 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" \ @@ -91,6 +133,9 @@ copyFiles_action() { "$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 \ diff --git a/lib/copy/check b/lib/copy/check index 44738dd..c6a37ad 100644 --- a/lib/copy/check +++ b/lib/copy/check @@ -1,12 +1,39 @@ #!/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. + checkCopy() { + # Returns true only when the source file is compatible enough with the + # destination profile that it can be copied instead of re-encoded. + # All three conditions must hold simultaneously. ( + # If the destination doesn't restrict sample rate, any rate is + # acceptable. Otherwise the source rate must match exactly. [ -z "${destinationfrequency[$destination]}" ] \ || (( ${rate:-0} == ${destinationfrequency[$destination]} )) ) && ( + # If the destination doesn't restrict channel count, any number + # is acceptable. Otherwise the source channel count must match + # exactly. [ -z "${destinationchannels[$destination]}" ] \ || (( ${channels:-0} == ${destinationchannels[$destination]} )) ) && ( + # Bitrate check: accept if source exactly matches the target + # quality setting, OR if a maximum bps ceiling is configured + # and the source bitrate is at or below it. + # Default of 1000 kbps when bitrate is unknown forces a + # re-encode (( ${bitrate:-1000} == ${destinationquality[$destination]} )) \ || ( [ -n "${destinationmaxbps[$destination]}" ] \ diff --git a/lib/copy/guessPath b/lib/copy/guessPath index f7f0926..b60b7c7 100644 --- a/lib/copy/guessPath +++ b/lib/copy/guessPath @@ -1,6 +1,29 @@ #!/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. + guessPath() { + # For copy files (action=2) with a rename pattern, we don't know the + # output directory ourselves: it was determined when the audio siblings + # were transcoded (action=1). We infer it by finding a transcoded + # sibling in the same source directory and reading back its destination + # path. + # + # First query: check whether any transcoded sibling is up to date. + # The LIKE pattern matches files directly inside $sourcedir only + # the NOT LIKE excludes deeper subdirectorie to avoid crossing + # boundaries. echo 'SELECT IFNULL( ( SELECT destination_files.last_change FROM destination_files @@ -22,10 +45,17 @@ guessPath() { ),"0.0"); '>&3 read -u4 -r -d $'\0' timestamp + # IFNULL returns "0.0" when no transcoded sibling exists yet; strip the + # decimal and treat as zero to detect the no-sibling case. if (( ${timestamp/./} == 0 )) then + # No transcoded sibling found at all + # caller should postpone this copy. return 2 fi + # Second query: retrieve the actual destination filename of the most + # recently updated transcoded sibling so we can derive its parent + # directory. echo 'SELECT IFNULL( ( SELECT destination_files.filename FROM destination_files @@ -49,8 +79,12 @@ guessPath() { read -u4 -r -d $'\0' filename if [[ $filename != AtOM:NotFound ]] then + # Strip the filename component to return only the directory + # portion. echo "${filename%/*}" else + # Sibling record exists but has no usable filename — skip this + # file. return 1 fi } diff --git a/lib/copy/matching b/lib/copy/matching index 73087a0..5240a0b 100644 --- a/lib/copy/matching +++ b/lib/copy/matching @@ -1,6 +1,25 @@ #!/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_matching() { + # Preserve the original file extension so the copy keeps its type + # (e.g. .jpg, .png, .cue) regardless of any rename pattern applied to + # the base name. local extension="${filename##*.}" + # Try hardlink first (no extra disk space); fall back to a full data + # copy if the source and destination are on different filesystems. if \ cp -al \ "$sourcepath/$filename" \ @@ -10,6 +29,11 @@ copyFiles_matching() { "$sourcepath/$filename" \ "${destinationpath[$destination]}/$destdir/$destfile.$extension" then + # Record the new destination path and copy the source + # last_change timestamp via a subquery so the DB reflects when + # the source was last modified. + # old_filename captures the previous path so stale files can be + # cleaned up later. echo \ "UPDATE destination_files" \ "SET filename=" \