AtOM/atom
2025-01-23 00:20:32 +01:00

1105 lines
24 KiB
Bash
Executable File

#!/usr/bin/env bash
## Define exit codes
# General config errors [10-19]
ELOAD=10
EINTERVAL=11
ENOCFG=19
# Source cofig errors [20-29]
# Destination config errors [30-49]
EFORMAT=30
ECHANNEL=31
EFMTINVPARM=49
# config structures
declare -A \
destinationenabled \
destinationascii \
destinationchannels \
destinationfat32compat \
destinationcopymime \
destinationformat \
destinationfrequency \
destinationid \
destinationloss \
destinationmaxbps \
destinationnormalize \
destinationpath \
destinationquality \
destinationrename \
destinationnoresample \
destinationrenamepath \
destinationskipmime \
|| {
echo "Check your Bash version. You need >= 4.0" >&2
exit $EBASHVERS
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
source $SHAREDIR/id3genres
for function in "$LIBDIR"/*/*
do
source "$function"
done
help() {
cat <<-EOF
Options:
-c <file> Load configuration file <file>
-C Dump configuration and exit
-l <load> Override max-load
-f <workers> Use exactly <workers> child processes
-T <seconds> override load-interval
-F <destination> Force re-generation of all files in
<destination>
-B <batch size> Create/update no more than <batch size> files
-S Run setup
-h Show this text
-D Increase debug
-q Cron mode
EOF
}
#parse arguments
OPTERR=0
while getopts ':c:Cl:T:F:f:B:ShDq' opt
do
case $opt in
c)
cffile="$OPTARG"
;;
C)
cfgdump=1
;;
l)
cliload="$OPTARG"
;;
T)
cliltimer="$OPTARG"
;;
F)
forceall+=("$OPTARG")
;;
f)
fixed_workers="$OPTARG"
;;
B)
maxbatch="$OPTARG"
;;
S)
forcesetup=1
;;
h)
help
exit 0
;;
D)
(( debug++ ))
;;
q)
cron=1
;;
:)
echo "-$OPTARG requires an argument"
help
exit 127
;;
*)
echo "Unrecognized option: -$OPTARG"
help
exit 127
;;
esac
done
askconf() {
if (( cron ))
then
echo 'Non-interactive, not running setup. Please run atom -S.' >&2
createconf=n
else
read -p"Create one now? [Y/n/q] " createconf
fi
case $createconf in
''|[yY])
setup
;;
[nNqQ])
echo "You need a configuration file. If you" \
"want to create it yourself, please" \
"read doc/config and doc/example.cfg." >&2
exit $ENOCFG
;;
*)
echo "Come again?" >&2
askconf
;;
esac
}
if [ ! -f "$cffile" ]
then
if [ ! -d "${cffile%/*}" ]
then
mkdir -p "${cffile%/*}"
fi
echo "No configuration file found!" >&2
askconf
fi
getConfig
(( forcesetup )) && setup
set +H
# Apply CLI overrides
[ -n "$cliload" ] && maxload=$cliload
[ -n "$cliltimer" ] && loadinterval=$cliltimer
(( debug || cfgdump )) && printConfig
(( cfgdump )) && exit
# check sanity
if [ ! -d "$tempdir" ] && ! mkdir -p "$tempdir"
then
echo "[FATAL] Could not create temp directory $tempdir" >&2
(( sanityfail++ ))
fi
if [ ! -f "$database" ] && [ ! -d "${database%/*}" ] && ! mkdir -p "${database%/*}"
then
echo "[FATAL] Directory holding database file does not exist and could" \
"not be created" >&2
(( sanityfail++ ))
fi
if [ ! -d "$sourcepath" ]
then
echo "[FATAL] Source path $sourcepath does not exist or is not a directory" >&2
(( sanityfail++ ))
fi
if ! which sed >/dev/null
then
echo "[FATAL] Required tool sed is not installed or not in PATH
I never thought this would actually hit someone..." >&2
(( sanityfail++ ))
fi
if ! which sox >/dev/null
then
echo "[FATAL] Required tool sox is not installed or not in PATH" >&2
(( sanityfail++ ))
fi
if ! which ogginfo >/dev/null
then
echo "[WARNING] Tool ogginfo (from vorbis-tools) is not" \
"installed or not in PATH
WebM metadata disabled" >&2
disableogginfo=1
(( sanitywarn++ ))
fi
if ! which soxi >/dev/null
then
echo "[WARNING] Tool soxi (from sox) is not" \
"installed or not in PATH
Vorbis metadata disabled" >&2
disablesoxi=1
(( sanitywarn++ ))
fi
if (( oggencneeded )) && ! which oggenc >/dev/null
then
echo "[WARNING] Tool oggenc (from vorbis-tools) is not" \
"installed or not in PATH
Vorbis targets disabled" >&2
disableoggenc=1
(( sanitywarn++ ))
fi
if ! which opusinfo >/dev/null
then
echo "[WARNING] Tool opusinfo (from opus-tools) is not" \
"installed or not in PATH
Opus metadata disabled" >&2
disableopusinfo=1
(( sanitywarn++ ))
fi
if (( opusencneeded )) && ! which opusenc >/dev/null
then
echo "[WARNING] Tool opusenc (from opus-tools) is not" \
"installed or not in PATH
Opus targets disabled" >&2
disableopusenc=1
(( sanitywarn++ ))
fi
if ! which opusdec >/dev/null
then
echo "[WARNING] Tool opusdec (from opus-tools) is not" \
"installed or not in PATH
Opus support disabled" >&2
disableopusdec=1
(( sanitywarn++ ))
fi
if (( lameneeded )) && ! which lame >/dev/null
then
echo "[WARNING] Tool lame is not installed or not in PATH
MP3 targets disabled" >&2
disablelame=1
(( sanitywarn++ ))
fi
if ! which metaflac >/dev/null
then
echo "[WARNING] Tool metaflac (from FLAC) is not installed" \
"or not in PATH
FLAC metadata disabled" >&2
disableflac=1
(( sanitywarn++ ))
fi
if ! which mpcdec >/dev/null
then
echo "[WARNING] Tool mpcdec (from Musepack) is not" \
"installed or not in PATH
Musepack support disabled" >&2
disablempcdec=1
(( sanitywarn++ ))
fi
if ! which mkvextract >/dev/null
then
echo "[WARNING] Tool mkvextract (from MKVToolNix) is not" \
"installed or not in PATH
WebM metadata disabled
WebM support disabled" >&2
disablemkvextract=1
(( sanitywarn++ ))
fi
if ! which ffprobe >/dev/null
then
echo "[WARNING] Tool ffprobe (from FFmpeg) is not installed or not in PATH
Video metadata disabled
MPEG metadata disabled
MusePack metadata disabled
Unknown format metadata disabled" >&2
disableffprobe=1
(( sanitywarn++ ))
fi
if ! which ffmpeg >/dev/null
then
echo "[WARNING] Tool ffmpeg is not installed or not in PATH
Video support disabled" >&2
disablevideo=1
(( sanitywarn++ ))
fi
if (( textunidecodeneeded )) && ! perl -MText::Unidecode -e 'exit;' 2>/dev/null
then
echo "[WARNING] Perl module Text::Unidecode is not available
Renaming to ASCII-only disabled" >&2
unset destinationascii
destinationascii=0
textunidecodeneeded=0
(( sanitywarn++ ))
fi
if (( sanityfail ))
then
echo "
Sanity checks raised ${sanitywarn:-0} warnings, $sanityfail failures. Dying now." >&2
exit $ESANITY
elif (( sanitywarn ))
then
echo "
Sanity checks raised $sanitywarn warnings... Hit Control-C to abort." >&2
if ! (( cron ))
then
timeout=$(( sanitywarn * 10 ))
echo -n "Starting in $(printf %3i $timeout)" \
$'seconds...\b\b\b\b\b\b\b\b\b\b\b' >&2
while (( timeout ))
do
echo -n $'\b\b\b'"$(printf %3i $timeout)" >&2
sleep 1
(( timeout-- ))
done
echo -en "\r\033[K"
fi
fi
openDatabase
for destination in "${destinations[@]}"
do
if (( ${destinationenabled["$destination"]} ))
then
Update destinations enabled 1 <<<"name = $destination"
else
Update destinations enabled 0 <<<"name = $destination"
fi
done
createDestinations
getFiles
updateMimes
removeObsoleteFiles
(( cron )) || echo -n 'Gathering files for cleaning...'
echo '
SELECT COUNT(id)
FROM destination_files
WHERE source_file_id is NULL;' >&3
read -u4 removecount
until (( ${#removefile[@]} == removecount ))
do
echo '
SELECT id,
filename
FROM destination_files
WHERE source_file_id is NULL
LIMIT 500 OFFSET '${#removefile[@]}';
SELECT "AtOM:NoMoreFiles";
' >&3
read -u4 line
until [[ $line == AtOM:NoMoreFiles ]]
do
removefile[${line%::AtOM:SQL:Sep::*}]="${line%::AtOM:SQL:Sep::*}"
read -u4 line
done
done
deleted=0
removed=0
echo 'BEGIN TRANSACTION;' >&3
for id in ${!removefile[@]}
do
filename=${removefile[id]}
if [ -n "$filename" ]
then
if rm -f "$filename"
then
Delete destination_files <<<"id = $id"
(( ++deleted ))
fi
else
Delete destination_files <<<"id = $id"
(( ++removed ))
fi
if (( (deleted + removed) % 1000 == 0 ))
then
echo 'COMMIT;BEGIN TRANSACTION;' >&3
fi
(( cron )) || echo -en "\rClean obsolete data: $(((deleted+removed)*100/removecount))%\033[K"
done
echo 'COMMIT;' >&3
(( cron )) || echo -n $'\r'
echo -n "Suppressed $deleted files, $removed removed from database"
(( cron )) || echo -ne "\033[K"
echo
unset removecount deleted removed removefile
updateTags
for forcedest in "${forceall[@]}"
do
if forcedestid=$(Select destinations id <<<"name = $forcedest")
then
echo "Resetting destination files timestamps on" \
"$forcedest ($forcedestid)..."
Update destination_files last_change 0 \
<<<"destination_id = $forcedestid"
else
echo "Destination $forcedest does not exist!" >&2
fi
done
echo '
CREATE TEMPORARY TABLE tasks(
id INTEGER PRIMARY KEY,
requires INTEGER,
required INTEGER,
status INTEGER NOT NULL,
key TEXT UNIQUE,
rename_pattern TEXT,
fat32compat INTEGER,
ascii INTEGER,
source_file INTEGER,
fileid INTEGER,
filename TEXT,
cmd_arg0 TEXT,
cmd_arg1 TEXT,
cmd_arg2 TEXT,
cmd_arg3 TEXT,
cmd_arg4 TEXT,
cmd_arg5 TEXT,
cmd_arg6 TEXT,
cmd_arg7 TEXT,
cmd_arg8 TEXT,
cmd_arg9 TEXT,
cmd_arg10 TEXT,
cmd_arg11 TEXT,
cmd_arg12 TEXT,
cmd_arg13 TEXT,
cmd_arg14 TEXT,
cmd_arg15 TEXT,
cmd_arg16 TEXT,
cmd_arg17 TEXT,
cmd_arg18 TEXT,
cmd_arg19 TEXT,
cmd_arg20 TEXT,
cmd_arg21 TEXT,
cmd_arg22 TEXT,
cmd_arg23 TEXT,
cmd_arg24 TEXT,
cmd_arg25 TEXT,
cmd_arg26 TEXT,
cmd_arg27 TEXT,
cmd_arg28 TEXT,
cmd_arg29 TEXT,
cmd_arg30 TEXT,
cmd_arg31 TEXT,
cmd_arg32 TEXT,
cmd_arg33 TEXT,
cmd_arg34 TEXT,
cmd_arg35 TEXT,
cmd_arg36 TEXT,
cmd_arg37 TEXT,
cmd_arg38 TEXT,
cmd_arg39 TEXT,
cmd_arg40 TEXT,
cmd_arg41 TEXT,
cmd_arg42 TEXT,
cmd_arg43 TEXT,
cmd_arg44 TEXT,
cmd_arg45 TEXT,
cmd_arg46 TEXT,
cmd_arg47 TEXT,
cmd_arg48 TEXT,
cmd_arg49 TEXT,
cmd_arg50 TEXT,
cmd_arg51 TEXT,
cmd_arg52 TEXT,
cmd_arg53 TEXT,
cmd_arg54 TEXT,
cmd_arg55 TEXT,
cmd_arg56 TEXT,
cmd_arg57 TEXT,
cmd_arg58 TEXT,
cmd_arg59 TEXT,
cleanup TEXT,
FOREIGN KEY(requires) REFERENCES tasks(id)
ON DELETE SET NULL
);
CREATE INDEX tasks_by_key ON tasks ( key );
CREATE INDEX tasks_by_sourcefile ON tasks ( source_file );
CREATE TEMPORARY TRIGGER fail_depends
AFTER UPDATE OF
status
ON
tasks
WHEN
NEW.status=2
BEGIN
UPDATE tasks SET status=2 WHERE requires=NEW.id;
END;
' >&3
echo '
SELECT COUNT(source_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
INNER JOIN tags
ON source_files.id = tags.source_file
WHERE destinations.enabled = 1
AND 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 = 1;' >&3
read -u4 filecount
if [ -n "$maxbatch" ] && (( maxbatch < filecount ))
then
(( togo = filecount - maxbatch ))
filecount=$maxbatch
fi
echo '
SELECT
source_files.id,
source_files.filename,
mime_type_actions.mime_text,
destinations.name,
destination_files.id,
tags.depth,
tags.rate,
tags.channels,
tags.bitrate,
tags.genre,
tags.albumartist,
tags.year,
tags.album,
tags.disc,
tags.artist,
tags.track,
tags.title,
tags.composer,
tags.performer
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
INNER JOIN tags
ON source_files.id = tags.source_file
WHERE destinations.enabled = 1
AND 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 = 1
ORDER BY source_files.id' >&3
(( maxbatch )) && echo "LIMIT $maxbatch" >&3
echo ';
SELECT "AtOM:NoMoreFiles";' >&3
read -u4 line
while ! [[ $line = AtOM:NoMoreFiles ]]
do
decodefiles+=("$line::AtOM:SQL:Sep::")
read -u4 line
done
(( cron )) || echo -n 'Creating tasks... '
(( textunidecodeneeded )) && ascii
echo 'BEGIN TRANSACTION;' >&3
for line in "${decodefiles[@]}"
do
fileid=${line%%::AtOM:SQL:Sep::*}
rest=${line#*::AtOM:SQL:Sep::}
filename=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
mimetype=${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::}
bitdepth=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
rate=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
channels=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
bitrate=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
genre=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
albumartist=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
year=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
album=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
disc=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
artist=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
track=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
title=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
composer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
performer=${rest%%::AtOM:SQL:Sep::*}
unset rest
case ${destinationformat["$destination"]} in
vorbis) (( disableoggenc )) && continue ;;
opus) (( disableopusenc )) && continue ;;
mp3) (( disablelame )) && continue ;;
esac
decodeFile
getDestDir
getDestFile
for copy_ext in "${destinationcopyext[@]}"
do
if [[ $filename =~ '.*\.'$copy_ext'$' ]]
then
copied=1
break
fi
done
if (( copied ))
then
copyFiles_matching
else
encodeFile::${destinationformat[$destination]}
fi
unset \
album \
albumartist \
artist \
bitdepth \
bitrate \
channels \
commandline \
composer \
copied \
decodetaskid \
destfileid \
destination \
disc \
fileid \
filename \
mimetype \
performer \
rate \
rest \
sox_needed \
soxoptions_in \
soxoptions_out \
soxtaskid \
title \
track \
year \
tmpfile
done
echo 'COMMIT;' >&3
(( cron )) || echo -n $'\r'
echo "Created ${count:-0} tasks for $filecount files ${togo:+($togo left) }(${copies:-0} immediate copies)"
# remove perl unicode to ascii coprocess
(( textunidecodeneeded )) && eval exec "${toascii[1]}>&-"
concurrency=$(( maxload / 2 ))
(( concurrency )) || concurrency=1
active=0
concurrencychange=$(date +%s)
starttime=$concurrencychange
taskcount=$count
remaining=$taskcount
failed=0
while (( (remaining || ${#workers[@]}) && ! quit ))
do
read humanload garbage < /proc/loadavg
load=${humanload%.*}
if (( fixed_workers ))
then
concurrency="$fixed_workers"
else
if [ -z "$quit" ] \
&& (( ! pause )) \
&& (( $(date +%s)-concurrencychange >= loadinterval ))
then
if (( concurrency > 1 )) \
&& (( load > maxload ))
then
concurrencychange=$(date +%s)
(( --concurrency ))
elif (( load < maxload )) && (( active > concurrency - 1 ))
then
concurrencychange=$(date +%s)
(( ++concurrency ))
fi
fi
fi
checkworkers
cleaner
(( pause )) || master
if (( ran - failed ))
then
currenttime=$(date +%s)
if (( pause ))
then
(( runtime = pausestart - starttime - pausedtime ))
else
(( runtime = currenttime - starttime - pausedtime ))
fi
avgduration=$((
( runtime * 1000)
/
( ran - failed )
))
secsremaining=$(( remaining * avgduration / 1000 ))
(( days =
secsremaining
/
( 24*60*60 )
)) || true
(( hours =
( secsremaining - ( days*24*60*60 ) )
/
( 60*60 )
)) || true
(( minutes =
( secsremaining - ( ( days*24 + hours ) *60*60 ) )
/
60
)) || true
(( seconds =
secsremaining
-
( ( ( ( days*24 + hours ) *60 ) + minutes ) *60 )
)) || true
avgduration=$(printf %04i $avgduration)
avgdsec=${avgduration:0:-3}
avgdmsec=${avgduration#$avgdsec}
fi
fmtload='L:%4.1f/%i'
fmtworkers='W:%i/%i'
fmtprogress="T:%${#taskcount}i/%i (F:%i) %3i%%"
fmttime='%2id %2ih%02im%02is (A:%4.1fs/task)'
eta="ETA:$(
date -d "${days:-0} days
${hours:-0} hours
${minutes:-0} minutes
${seconds:-0} seconds" \
+'%d/%m %H:%M:%S'
)"
(( cron )) || printf \
"\r$fmtload $fmtworkers $fmtprogress $fmttime $eta\033[K"\
$humanload \
$maxload \
${active:-0} \
${concurrency:-0} \
${ran:-0} \
${taskcount:-0} \
${failed:-0} \
$(( ran * 100 / taskcount )) \
${days:-0} \
${hours:-0} \
${minutes:-0} \
${seconds:-0} \
${avgdsec:-0}.${avgdmsec:-0}
if (( pause ))
then
if (( active ))
then
echo -n ' | (pause)'
else
echo -n ' | PAUSED'
fi
fi
done
unset count
endtime=$(date +%s)
(( elapsedseconds = endtime - starttime - pausedtime ))
(( days =
elapsedseconds
/
( 24*60*60 )
)) || true
(( hours =
( elapsedseconds - ( days*24*60*60 ) )
/
( 60*60 )
)) || true
(( minutes =
( elapsedseconds - ( ( days*24 + hours ) *60*60 ) )
/
60
)) || true
(( seconds =
elapsedseconds
-
( ( ( ( days*24 + hours ) *60 ) + minutes ) *60 )
)) || true
(( cron )) || echo -n $'\r'
echo -n "Ran ${ran:=0} tasks, $failed of which failed, in $days" \
"days, $hours hours, $minutes minutes and $seconds seconds."
(( cron )) || echo -en "\033[K"
echo
if (( failed ))
then
echo $'\nFailed tasks:\n'
echo '
SELECT source_files.filename,
tasks.cmd_arg0,
tasks.cmd_arg1,
tasks.cmd_arg2,
tasks.cmd_arg3,
tasks.cmd_arg4,
tasks.cmd_arg5,
tasks.cmd_arg6,
tasks.cmd_arg7,
tasks.cmd_arg8,
tasks.cmd_arg9,
tasks.cmd_arg10,
tasks.cmd_arg11,
tasks.cmd_arg12,
tasks.cmd_arg13,
tasks.cmd_arg14,
tasks.cmd_arg15,
tasks.cmd_arg16,
tasks.cmd_arg17,
tasks.cmd_arg18,
tasks.cmd_arg19,
tasks.cmd_arg20,
tasks.cmd_arg21,
tasks.cmd_arg22,
tasks.cmd_arg23,
tasks.cmd_arg24,
tasks.cmd_arg25,
tasks.cmd_arg26,
tasks.cmd_arg27,
tasks.cmd_arg28,
tasks.cmd_arg29,
tasks.cmd_arg30,
tasks.cmd_arg31,
tasks.cmd_arg32,
tasks.cmd_arg33,
tasks.cmd_arg34,
tasks.cmd_arg35,
tasks.cmd_arg36,
tasks.cmd_arg37,
tasks.cmd_arg38,
tasks.cmd_arg39,
tasks.cmd_arg40,
tasks.cmd_arg41,
tasks.cmd_arg42,
tasks.cmd_arg43,
tasks.cmd_arg44,
tasks.cmd_arg45,
tasks.cmd_arg46,
tasks.cmd_arg47,
tasks.cmd_arg48,
tasks.cmd_arg49,
tasks.cmd_arg50,
tasks.cmd_arg51,
tasks.cmd_arg52,
tasks.cmd_arg53,
tasks.cmd_arg54,
tasks.cmd_arg55,
tasks.cmd_arg56,
tasks.cmd_arg57,
tasks.cmd_arg58,
tasks.cmd_arg59
FROM tasks
INNER JOIN source_files
ON tasks.source_file=source_files.id
WHERE tasks.status = 2
AND requires is NULL;
SELECT "AtOM:NoMoreFiles";' >&3
read -u4 line
while ! [[ $line = AtOM:NoMoreFiles ]]
do
failedtasks+=("$line")
read -u4 line
done
for line in "${failedtasks[@]}"
do
echo "${line%%::AtOM:SQL:Sep::*}"
line="${line#*::AtOM:SQL:Sep::}"
line="${line//::AtOM:SQL:Sep::/ }"
echo $'\t'"${line/+( )$/}"
echo
done
fi
if [ -n "$quit" ]
then
closeDatabase
exit
fi
for destination in "${!destinationpath[@]}"
do
echo '
SELECT
destination_files.filename,
destination_files.id,
source_files.filename,
tags.album,
tags.albumartist,
tags.artist,
tags.composer,
tags.disc,
tags.genre,
tags.performer,
tags.title,
tags.track,
tags.year
FROM destination_files
INNER JOIN destinations
ON destination_files.destination_id
=destinations.id
INNER JOIN tags
ON destination_files.source_file_id
=tags.source_file
INNER JOIN source_files
ON destination_files.source_file_id
=source_files.id
INNER JOIN mime_actions
ON source_files.mime_type
=mime_actions.mime_type
WHERE destinations.name="'"$destination"'"
AND (destination_files.rename_pattern
!=
"'"${destinationrenamepath[$destination]}/${destinationrename[$destination]}"'"
OR fat32compat != '${destinationfat32compat["$destination"]}'
OR ascii != '${destinationascii["$destination"]}'
OR destination_files.rename_pattern is NULL)
AND destination_files.last_change > 0
AND mime_actions.action=1
;
SELECT "AtOM:NoMoreFiles";
' >&3
read -u4 line
while [[ $line != AtOM:NoMoreFiles ]]
do
renamefiles+=("$line")
read -u4 line
done
if (( ${#renamefiles[@]} ))
then
case "${destinationformat[$destination]}" in
'mp3') extension=mp3 ;;
'opus') extension=opus ;;
'vorbis') extension=ogg ;;
esac
(( cron )) || echo -en "$destination: rename pattern changed, renaming files...\033[K"
(( textunidecodeneeded )) && ascii
echo 'BEGIN TRANSACTION;' >&3
for line in "${renamefiles[@]}"
do
oldfilename=${line%%::AtOM:SQL:Sep::*}
rest=${line#*::AtOM:SQL:Sep::}'::AtOM:SQL:Sep::'
destfileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
filename=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
album=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
albumartist=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
artist=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
composer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
disc=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
genre=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
performer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
title=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
track=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
year=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
if [ -n "$oldfilename" -a -f "$oldfilename" ]
then
getDestDir
getDestFile
destfilename="$destdir/$destfile.$extension"
progressSpin
if [[ "$oldfilename" != "$destfilename" ]]
then
mv "$oldfilename" "$destfilename"
(( changedcount++ ))
commit=1
fi
echo "UPDATE destination_files" \
"SET filename=\"${destfilename//\"/\"\"}\"," \
" rename_pattern=" \
"\"${destinationrenamepath[$destination]}/${destinationrename[$destination]}\","\
" fat32compat=" \
"${destinationfat32compat["$destination"]}," \
" ascii=" \
"${destinationascii["$destination"]}" \
"WHERE id=$destfileid;" \
>&3
if (( commit ))
then
echo $'COMMIT;\nBEGIN TRANSACTION;' >&3
unset commit
fi
fi
done
# remove perl unicode to ascii coprocess
(( textunidecodeneeded )) && eval exec "${toascii[1]}>&-"
echo 'COMMIT;' >&3
(( cron )) || echo -n $'\r'
echo -n "$destination: Renamed ${changedcount:-0} files"
(( cron )) || echo -en "\033[K"
echo
fi
unset count changedcount renamefiles
done
copyFiles_action
echo '
SELECT id,
filename,
old_filename
FROM destination_files
WHERE old_filename IS NOT NULL;
SELECT "AtOM:NoMoreFiles";
' >&3
(( cron )) || echo -n 'Removing obsolete files... '
lines=()
read -u4 line
while [[ $line != AtOM:NoMoreFiles ]]
do
lines+=("$line")
read -u4 line
done
echo 'BEGIN TRANSACTION;' >&3
for line in "${lines[@]}"
do
id=${line%%::AtOM:SQL:Sep::*}
rest=${line#*::AtOM:SQL:Sep::}
filename=${rest%%::AtOM:SQL:Sep::*}
oldfilename=${rest#*::AtOM:SQL:Sep::}
if [[ $oldfilename != "$filename" ]] && [ -f "$oldfilename" ]
then
rm -f "$oldfilename"
fi
Update destination_files old_filename NULL <<<"id = $id"
(( count++ ))
(( cron )) || printf '\b\b\b\b%3i%%' $(( (100 * count) / ${#lines[@]} ))
done
echo 'COMMIT;' >&3
(( cron )) || echo -n $'\r'
echo -n "Removed ${count:-0} obsolete files."
(( cron )) || echo -en "\033[K"
echo
echo "Purging empty directories."
for path in "${destinationpath[@]}"
do
find "$path" -type d -empty -delete
done
closeDatabase
# vim:set ts=8 sw=8: