Compare commits
No commits in common. "code-documentation" and "master" have entirely different histories.
code-docum
...
master
149
atom
149
atom
@ -1,21 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
# Directories for various required data. Set by the configure script and the
|
|
||||||
# Makefile.
|
|
||||||
# Also save $IFS just in case.
|
|
||||||
declare -r \
|
declare -r \
|
||||||
DOCDIR=%DOCDIR% \
|
DOCDIR=%DOCDIR% \
|
||||||
LIBDIR=%LIBDIR% \
|
LIBDIR=%LIBDIR% \
|
||||||
@ -29,7 +13,7 @@ declare -r \
|
|||||||
## Define exit codes
|
## Define exit codes
|
||||||
source "$SHAREDIR"/errorcodes
|
source "$SHAREDIR"/errorcodes
|
||||||
|
|
||||||
# Config structures.
|
# config structures
|
||||||
declare -A \
|
declare -A \
|
||||||
destinationenabled \
|
destinationenabled \
|
||||||
destinationascii \
|
destinationascii \
|
||||||
@ -54,13 +38,10 @@ declare -A \
|
|||||||
exit $EBASHVERS
|
exit $EBASHVERS
|
||||||
}
|
}
|
||||||
|
|
||||||
# Locales break parsingg.
|
|
||||||
LC_ALL=C
|
LC_ALL=C
|
||||||
|
|
||||||
# Enable extended globbing for some filename manipulations.
|
|
||||||
shopt -s extglob
|
shopt -s extglob
|
||||||
|
|
||||||
# Array of ID3v1 genres, number to name mapping.
|
|
||||||
source "$SHAREDIR"/id3genres
|
source "$SHAREDIR"/id3genres
|
||||||
|
|
||||||
for function in "$LIBDIR"/*/*
|
for function in "$LIBDIR"/*/*
|
||||||
@ -68,7 +49,6 @@ do
|
|||||||
source "$function"
|
source "$function"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Migrate old config to XDG where required
|
|
||||||
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
|
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
|
||||||
&& [[ -f "$HOME/.atom/atom.cfg" ]]
|
&& [[ -f "$HOME/.atom/atom.cfg" ]]
|
||||||
then
|
then
|
||||||
@ -148,9 +128,6 @@ do
|
|||||||
done
|
done
|
||||||
|
|
||||||
askconf() {
|
askconf() {
|
||||||
# Prompt user to interactively create a config if it doesn't exist and
|
|
||||||
# we're not in cron mode.
|
|
||||||
# Called recursively until a valid answer is given.
|
|
||||||
if (( cron ))
|
if (( cron ))
|
||||||
then
|
then
|
||||||
echo 'Non-interactive, not running setup. Please run atom -S.' >&2
|
echo 'Non-interactive, not running setup. Please run atom -S.' >&2
|
||||||
@ -175,7 +152,6 @@ askconf() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# Read config if it exists, call askconf otherwise.
|
|
||||||
if [ ! -f "$cffile" ]
|
if [ ! -f "$cffile" ]
|
||||||
then
|
then
|
||||||
if [ ! -d "${cffile%/*}" ]
|
if [ ! -d "${cffile%/*}" ]
|
||||||
@ -187,17 +163,14 @@ then
|
|||||||
fi
|
fi
|
||||||
getConfig
|
getConfig
|
||||||
|
|
||||||
# Uf user wants to changeg config, run setup.
|
|
||||||
(( forcesetup )) && setup
|
(( forcesetup )) && setup
|
||||||
|
|
||||||
# Deactivate `!` history expansion, just in case.
|
|
||||||
set +H
|
set +H
|
||||||
|
|
||||||
# Apply CLI overrides
|
# Apply CLI overrides
|
||||||
[ -n "$cliload" ] && maxload=$cliload
|
[ -n "$cliload" ] && maxload=$cliload
|
||||||
[ -n "$cliltimer" ] && loadinterval=$cliltimer
|
[ -n "$cliltimer" ] && loadinterval=$cliltimer
|
||||||
|
|
||||||
# Print config if requested or in debug mode. Exit if only dumping config.
|
|
||||||
(( debug || cfgdump )) && printConfig
|
(( debug || cfgdump )) && printConfig
|
||||||
(( cfgdump )) && exit
|
(( cfgdump )) && exit
|
||||||
|
|
||||||
@ -205,11 +178,6 @@ set +H
|
|||||||
sanityCheck
|
sanityCheck
|
||||||
openDatabase
|
openDatabase
|
||||||
|
|
||||||
# create missing destination directories and DB entries, get destination IDs
|
|
||||||
# for later use
|
|
||||||
createDestinations
|
|
||||||
|
|
||||||
# Apply destinations "enabled" status in DB with respect to config.
|
|
||||||
for destination in "${destinations[@]}"
|
for destination in "${destinations[@]}"
|
||||||
do
|
do
|
||||||
if (( ${destinationenabled["$destination"]} ))
|
if (( ${destinationenabled["$destination"]} ))
|
||||||
@ -220,20 +188,14 @@ do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# get source files. Update DB.
|
createDestinations
|
||||||
|
|
||||||
getFiles
|
getFiles
|
||||||
|
|
||||||
# Scan mime-types (for new/changed files). Update DB
|
|
||||||
updateMimes
|
updateMimes
|
||||||
|
|
||||||
# Remove source files that are gone from DB. (`last_seen` column).
|
|
||||||
# FOREIGN KEY `source_file_id` on table `destination_files` gets set to NULL
|
|
||||||
# by `ON DELETE` parameter. We can use that to find destination files that need
|
|
||||||
# to be removed.
|
|
||||||
removeObsoleteFiles
|
removeObsoleteFiles
|
||||||
|
|
||||||
# remove destination files for which the source file is gone. (`source_file_id`
|
|
||||||
# NUUL -- `rm` them if they exist, remove the DB entry in any case)
|
|
||||||
(( cron )) || echo -n 'Gathering files for cleaning...'
|
(( cron )) || echo -n 'Gathering files for cleaning...'
|
||||||
echo '
|
echo '
|
||||||
SELECT COUNT(id)
|
SELECT COUNT(id)
|
||||||
@ -241,7 +203,6 @@ echo '
|
|||||||
WHERE source_file_id is NULL;' >&3
|
WHERE source_file_id is NULL;' >&3
|
||||||
|
|
||||||
read -u4 -r -d $'\0' removecount
|
read -u4 -r -d $'\0' removecount
|
||||||
# Gather in 500 files batches to avoid pipe overflow.
|
|
||||||
until (( ${#removefile[@]} == removecount ))
|
until (( ${#removefile[@]} == removecount ))
|
||||||
do
|
do
|
||||||
echo '
|
echo '
|
||||||
@ -273,10 +234,6 @@ done
|
|||||||
unset deleted
|
unset deleted
|
||||||
unset removed
|
unset removed
|
||||||
echo 'BEGIN TRANSACTION;' >&3
|
echo 'BEGIN TRANSACTION;' >&3
|
||||||
|
|
||||||
# Remove the files if they exist. Unconditionnally remove from DB.
|
|
||||||
# Run in transactrion to speed up the process, COMMIT every 1000th file in case
|
|
||||||
# process gets killed.
|
|
||||||
for id in ${!removefile[@]}
|
for id in ${!removefile[@]}
|
||||||
do
|
do
|
||||||
filename=${removefile[id]}
|
filename=${removefile[id]}
|
||||||
@ -304,14 +261,8 @@ echo -n "${deleted+$deleted files deleted${removed:+, }}${removed:+$removed remo
|
|||||||
(( deleted || removed )) && echo
|
(( deleted || removed )) && echo
|
||||||
unset removecount deleted removed removefile
|
unset removecount deleted removed removefile
|
||||||
|
|
||||||
# Update tags for new/changed files and updated tag parsers. Update DB.
|
|
||||||
# Uses `tags.last_changge` vs `source_files.last_change`
|
|
||||||
# + `tags.tagreader` vs tagreader versions declared in running version's
|
|
||||||
# source code.
|
|
||||||
updateTags
|
updateTags
|
||||||
|
|
||||||
# Reset timestamps for files in destinations that were requested to be fully
|
|
||||||
# rebuilt.
|
|
||||||
for forcedest in "${forceall[@]}"
|
for forcedest in "${forceall[@]}"
|
||||||
do
|
do
|
||||||
if forcedestid=$(Select destinations id <<<"name = $forcedest")
|
if forcedestid=$(Select destinations id <<<"name = $forcedest")
|
||||||
@ -326,18 +277,6 @@ do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Create TEMPORARY (in-memory) tables for tasks. Up to 60 arguments per task
|
|
||||||
# (including command).
|
|
||||||
#
|
|
||||||
# `requires` designates the id of a tasks that must be completed before this
|
|
||||||
# one can be run.
|
|
||||||
# `required_by` is a counter of tasks that require this one. Used for temp
|
|
||||||
# files cleanup.
|
|
||||||
# `status` is 0 for pending tasks, 2 for failed tasks, 4 for completed tasks
|
|
||||||
# depended upon (temp file should be left intact).
|
|
||||||
#
|
|
||||||
# TRIGGER `fail_depends` sets status of all tasks that depend on a failed task
|
|
||||||
# to 2
|
|
||||||
echo '
|
echo '
|
||||||
CREATE TEMPORARY TABLE tasks(
|
CREATE TEMPORARY TABLE tasks(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
@ -431,7 +370,6 @@ echo '
|
|||||||
END;
|
END;
|
||||||
' >&3
|
' >&3
|
||||||
|
|
||||||
# Get number of files to process. Apply `maxbatch` limit if specified.
|
|
||||||
echo '
|
echo '
|
||||||
SELECT COUNT(source_files.id)
|
SELECT COUNT(source_files.id)
|
||||||
FROM source_files
|
FROM source_files
|
||||||
@ -455,8 +393,6 @@ then
|
|||||||
(( togo = filecount - maxbatch ))
|
(( togo = filecount - maxbatch ))
|
||||||
filecount=$maxbatch
|
filecount=$maxbatch
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get files to process. Apply `maxbatch` limit if specified.
|
|
||||||
echo '
|
echo '
|
||||||
SELECT
|
SELECT
|
||||||
source_files.id,
|
source_files.id,
|
||||||
@ -503,26 +439,16 @@ echo ';
|
|||||||
read -u4 -r -d $'\0' line
|
read -u4 -r -d $'\0' line
|
||||||
while ! [[ $line = AtOM:NoMoreFiles ]]
|
while ! [[ $line = AtOM:NoMoreFiles ]]
|
||||||
do
|
do
|
||||||
# Append `::AtOM:SQL:Sep::` at the end of the line to make sure we can
|
|
||||||
# parse empty fields.
|
|
||||||
decodefiles+=("$line::AtOM:SQL:Sep::")
|
decodefiles+=("$line::AtOM:SQL:Sep::")
|
||||||
read -u4 -r -d $'\0' line
|
read -u4 -r -d $'\0' line
|
||||||
done
|
done
|
||||||
(( cron )) || echo -n $'Creating tasks...\033[K'
|
(( cron )) || echo -n $'Creating tasks...\033[K'
|
||||||
|
|
||||||
# Spawn perl coprocess for unicode to ascii conversion if needed.
|
|
||||||
(( textunidecodeneeded )) && ascii
|
(( textunidecodeneeded )) && ascii
|
||||||
|
|
||||||
# Generate tasks for each file. Tasks that depend on other tasks (e.g. encoding
|
|
||||||
# depends on decoding) get the ID of the task they depend on in `requires`
|
|
||||||
# column. This is used to make sure that encoding doesn't start before decoding
|
|
||||||
# and other transforms have completed.
|
|
||||||
echo 'BEGIN TRANSACTION;' >&3
|
echo 'BEGIN TRANSACTION;' >&3
|
||||||
for line in "${decodefiles[@]}"
|
for line in "${decodefiles[@]}"
|
||||||
do
|
do
|
||||||
# Parsing SQL output is fun. We use `::AtOM:SQL:Sep::` as separator
|
|
||||||
# between fields at the end of the line to make sure we can parse empty
|
|
||||||
# fields.
|
|
||||||
fileid=${line%%::AtOM:SQL:Sep::*}
|
fileid=${line%%::AtOM:SQL:Sep::*}
|
||||||
rest=${line#*::AtOM:SQL:Sep::}
|
rest=${line#*::AtOM:SQL:Sep::}
|
||||||
filename=${rest%%::AtOM:SQL:Sep::*}
|
filename=${rest%%::AtOM:SQL:Sep::*}
|
||||||
@ -567,24 +493,14 @@ do
|
|||||||
rest=${rest#*::AtOM:SQL:Sep::}
|
rest=${rest#*::AtOM:SQL:Sep::}
|
||||||
year=${rest%%::AtOM:SQL:Sep::*}
|
year=${rest%%::AtOM:SQL:Sep::*}
|
||||||
unset rest
|
unset rest
|
||||||
|
|
||||||
# Skip destinations with formats for which tools are missing.
|
|
||||||
case ${destinationformat["$destination"]} in
|
case ${destinationformat["$destination"]} in
|
||||||
vorbis) (( disableoggenc )) && continue ;;
|
vorbis) (( disableoggenc )) && continue ;;
|
||||||
opus) (( disableopusenc )) && continue ;;
|
opus) (( disableopusenc )) && continue ;;
|
||||||
mp3) (( disablelame )) && continue ;;
|
mp3) (( disablelame )) && continue ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Create decoding task depending on mimetype.
|
|
||||||
decodeFile
|
decodeFile
|
||||||
|
|
||||||
# Build target directory path from source file path OR from rename
|
|
||||||
# pattern if set.
|
|
||||||
getDestDir
|
getDestDir
|
||||||
# Same for filename.
|
|
||||||
getDestFile
|
getDestFile
|
||||||
|
|
||||||
# Set copied to 1 for files with extension in `copy_extension`.
|
|
||||||
for copy_ext in "${destinationcopyext[@]}"
|
for copy_ext in "${destinationcopyext[@]}"
|
||||||
do
|
do
|
||||||
if [[ $filename =~ '.*\.'"$copy_ext"'$' ]]
|
if [[ $filename =~ '.*\.'"$copy_ext"'$' ]]
|
||||||
@ -595,17 +511,10 @@ do
|
|||||||
done
|
done
|
||||||
if (( copied ))
|
if (( copied ))
|
||||||
then
|
then
|
||||||
# Copy file as-is to destination.
|
|
||||||
copyFiles_matching
|
copyFiles_matching
|
||||||
else
|
else
|
||||||
# Call suitable function to create encoding task depending on
|
|
||||||
# destination format.
|
|
||||||
# encodeFile::mp3
|
|
||||||
# encodeFile::opus
|
|
||||||
# encodeFile::vorbis
|
|
||||||
encodeFile::${destinationformat[$destination]}
|
encodeFile::${destinationformat[$destination]}
|
||||||
fi
|
fi
|
||||||
# Cleanup variables. Avoids leaking data between iterations.
|
|
||||||
unset \
|
unset \
|
||||||
album \
|
album \
|
||||||
albumartist \
|
albumartist \
|
||||||
@ -648,13 +557,6 @@ echo 'COMMIT;' >&3
|
|||||||
# remove perl unicode to ascii coprocess
|
# remove perl unicode to ascii coprocess
|
||||||
(( textunidecodeneeded )) && eval exec "${toascii[1]}>&-"
|
(( textunidecodeneeded )) && eval exec "${toascii[1]}>&-"
|
||||||
|
|
||||||
# Main loop. Run up to `concurrency` tasks in parallel, depending on system
|
|
||||||
# load. Spawn new tasks as old ones complete. Update progress info and commit
|
|
||||||
# DB every minute.
|
|
||||||
#
|
|
||||||
# Start with concurrency = maxload / 2, which seems like a good starting point
|
|
||||||
# on most systems. If result is 0, force to 1 to make sure we get going. If
|
|
||||||
# `-f <workers>` option was used, use that value instead and don't change it.
|
|
||||||
concurrency=$(( maxload / 2 ))
|
concurrency=$(( maxload / 2 ))
|
||||||
(( concurrency )) || concurrency=1
|
(( concurrency )) || concurrency=1
|
||||||
active=0
|
active=0
|
||||||
@ -675,13 +577,6 @@ do
|
|||||||
fi
|
fi
|
||||||
read humanload garbage < /proc/loadavg
|
read humanload garbage < /proc/loadavg
|
||||||
load=${humanload%.*}
|
load=${humanload%.*}
|
||||||
# If `-f <workers>` option was used, keep concurrency fixed to that
|
|
||||||
# value. Otherwise, adjust concurrency according to load and `maxload`
|
|
||||||
# value. If load is above `maxload`, reduce concurrency by 1 (down to 0
|
|
||||||
# if `allow_zero_running` is set). If load is below `maxload`, increase
|
|
||||||
# concurrency by 1 (only if all slots are populated).
|
|
||||||
# Don't update concurrency more often than every `load-interval` seconds
|
|
||||||
# to reduce histeresis.
|
|
||||||
if (( fixed_workers ))
|
if (( fixed_workers ))
|
||||||
then
|
then
|
||||||
concurrency="$fixed_workers"
|
concurrency="$fixed_workers"
|
||||||
@ -701,30 +596,9 @@ do
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# check if workers have finished.
|
|
||||||
# If a worker finished with non-zero exit code, mark the task as failed
|
|
||||||
# in DB. TRIGGER `fail_depends` will fail all tasks that depend on it.
|
|
||||||
checkworkers
|
checkworkers
|
||||||
# If task failed, set status to 2, remove temp files if any and
|
|
||||||
# increment `failed` counter. Don't delete task from DB to keep track
|
|
||||||
# of failed tasks for final report.
|
|
||||||
# If task was successful, update destination_files.last_change to
|
|
||||||
# source_files.last_change, update old_filename to previous
|
|
||||||
# (destination file) filename, store rename_pattern, fat32compat and
|
|
||||||
# ascii settings.
|
|
||||||
# Set status to 4 for tasks that were depended upon by other tasks to
|
|
||||||
# avoid cleaning up temp files before all tasks depending on them have
|
|
||||||
# completed, delete task otherwise.
|
|
||||||
cleaner
|
cleaner
|
||||||
# Look for pending tasks that can be started (required tasks's status
|
|
||||||
# is 4 or reqquires is NULL) and start them.
|
|
||||||
# Trigger a dump of the tasks table if it's inconsistent (no running
|
|
||||||
# tasks, no ready tasks, but pending tasks exist) and exit with error
|
|
||||||
# $ETASKLEFT.
|
|
||||||
master
|
master
|
||||||
|
|
||||||
# "Fancy" progress info. Only calculate if at least one task has
|
|
||||||
# succeeded to avoid division by zero.
|
|
||||||
if (( ran - failed ))
|
if (( ran - failed ))
|
||||||
then
|
then
|
||||||
currenttime=$timestamp
|
currenttime=$timestamp
|
||||||
@ -763,7 +637,6 @@ do
|
|||||||
fmtworkers='W:%i/%i'
|
fmtworkers='W:%i/%i'
|
||||||
fmtprogress="T:%${#taskcount}i/%i (F:%i) %3i%%"
|
fmtprogress="T:%${#taskcount}i/%i (F:%i) %3i%%"
|
||||||
fmttime='%2id %2ih%02im%02is (A:%4.1fs/task)'
|
fmttime='%2id %2ih%02im%02is (A:%4.1fs/task)'
|
||||||
# Abuse timeformatting to get ETA.
|
|
||||||
eta="ETA:$(
|
eta="ETA:$(
|
||||||
printf "%(%c)T" "$(( currenttime + secsremaining ))"
|
printf "%(%c)T" "$(( currenttime + secsremaining ))"
|
||||||
)"
|
)"
|
||||||
@ -782,7 +655,6 @@ do
|
|||||||
${minutes:-0} \
|
${minutes:-0} \
|
||||||
${seconds:-0} \
|
${seconds:-0} \
|
||||||
${avgdsec:-0}.${avgdmsec:-0}
|
${avgdsec:-0}.${avgdmsec:-0}
|
||||||
# If 0 concurrency is allowed, show paused status when concurrency is 0
|
|
||||||
if ! (( concurrency )) && ! (( cron ))
|
if ! (( concurrency )) && ! (( cron ))
|
||||||
then
|
then
|
||||||
if (( active ))
|
if (( active ))
|
||||||
@ -796,7 +668,6 @@ done
|
|||||||
echo 'COMMIT;' >&3
|
echo 'COMMIT;' >&3
|
||||||
unset count
|
unset count
|
||||||
|
|
||||||
# Final report. Calculate elapsed time and format it in human readable way.
|
|
||||||
endtime=$EPOCHSECONDS
|
endtime=$EPOCHSECONDS
|
||||||
|
|
||||||
(( elapsedseconds = endtime - starttime ))
|
(( elapsedseconds = endtime - starttime ))
|
||||||
@ -831,9 +702,6 @@ endtime=$EPOCHSECONDS
|
|||||||
(( cron )) || echo -en "\033[K"
|
(( cron )) || echo -en "\033[K"
|
||||||
(( ran )) && echo
|
(( ran )) && echo
|
||||||
|
|
||||||
# If some tasks failed, print them. Don't print failed tasks that did not run
|
|
||||||
# to avoid confusing tasks marked as failed because they depended on ones that
|
|
||||||
# failed.
|
|
||||||
if (( failed ))
|
if (( failed ))
|
||||||
then
|
then
|
||||||
echo $'\nFailed tasks:\n'
|
echo $'\nFailed tasks:\n'
|
||||||
@ -920,8 +788,6 @@ then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if there are files that need to be renamed because their rename pattern
|
|
||||||
# changed.
|
|
||||||
for destination in "${!destinationpath[@]}"
|
for destination in "${!destinationpath[@]}"
|
||||||
do
|
do
|
||||||
echo '
|
echo '
|
||||||
@ -981,8 +847,6 @@ do
|
|||||||
'vorbis') extension=ogg ;;
|
'vorbis') extension=ogg ;;
|
||||||
esac
|
esac
|
||||||
(( cron )) || echo -en "$destination: rename pattern changed, renaming files...\033[K"
|
(( cron )) || echo -en "$destination: rename pattern changed, renaming files...\033[K"
|
||||||
# Spawn perl coprocess for unicode to ascii conversion if
|
|
||||||
# needed.
|
|
||||||
(( textunidecodeneeded )) && ascii
|
(( textunidecodeneeded )) && ascii
|
||||||
echo 'BEGIN TRANSACTION;' >&3
|
echo 'BEGIN TRANSACTION;' >&3
|
||||||
for line in "${renamefiles[@]}"
|
for line in "${renamefiles[@]}"
|
||||||
@ -1058,12 +922,8 @@ do
|
|||||||
unset count changedcount renamefiles
|
unset count changedcount renamefiles
|
||||||
done
|
done
|
||||||
|
|
||||||
# Copy files of mime-types matching `copy_mime-type`
|
|
||||||
copyFiles_action
|
copyFiles_action
|
||||||
|
|
||||||
# Remove files obsoleted by `renamme_pattern`, `ascii` or `fat32compat` changes.
|
|
||||||
# Based on `destination_files.old_filename` field, populated upon task
|
|
||||||
# completion.
|
|
||||||
echo '
|
echo '
|
||||||
SELECT destination_files.id,
|
SELECT destination_files.id,
|
||||||
destination_files.filename,
|
destination_files.filename,
|
||||||
@ -1110,9 +970,6 @@ echo 'COMMIT;' >&3
|
|||||||
(( cron )) || echo -en "\033[K"
|
(( cron )) || echo -en "\033[K"
|
||||||
(( count )) && echo
|
(( count )) && echo
|
||||||
|
|
||||||
# Remove empty directories in destinations.
|
|
||||||
# We blindly duplicate the source tree in the destination, so we may end up
|
|
||||||
# with empty directories.
|
|
||||||
(( debug )) && echo "Purging empty directories..."
|
(( debug )) && echo "Purging empty directories..."
|
||||||
for path in "${destinationpath[@]}"
|
for path in "${destinationpath[@]}"
|
||||||
do
|
do
|
||||||
|
|||||||
7
configure
vendored
7
configure
vendored
@ -1,28 +1,25 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Default install prefix if --prefix= is not provided
|
# defaults
|
||||||
default_prefix=/usr/local
|
default_prefix=/usr/local
|
||||||
#default_bindir=$default_prefix/bin
|
#default_bindir=$default_prefix/bin
|
||||||
#default_libdir=$default_prefix/lib
|
#default_libdir=$default_prefix/lib
|
||||||
#default_sharedir=$default_prefix/share
|
#default_sharedir=$default_prefix/share
|
||||||
|
|
||||||
# Parse command-line arguments (only --prefix=VALUE is supported)
|
|
||||||
while (( $# ))
|
while (( $# ))
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--prefix=*) prefix="${1#*=}" # Strip the "--prefix=" portion to get the value
|
--prefix=*) prefix="${1#*=}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
# Derive install directories from prefix (or default_prefix if unset)
|
|
||||||
bindir="${prefix:-$default_prefix}"/bin
|
bindir="${prefix:-$default_prefix}"/bin
|
||||||
libdir="${prefix:-$default_prefix}"/lib/AtOM
|
libdir="${prefix:-$default_prefix}"/lib/AtOM
|
||||||
sharedir="${prefix:-$default_prefix}"/share/AtOM
|
sharedir="${prefix:-$default_prefix}"/share/AtOM
|
||||||
docdir="${prefix:-$default_prefix}"/share/doc/AtOM
|
docdir="${prefix:-$default_prefix}"/share/doc/AtOM
|
||||||
|
|
||||||
# Write Makefile.in so the Makefile can substitute real paths at build time
|
|
||||||
cat > Makefile.in <<-EOMakefile.in
|
cat > Makefile.in <<-EOMakefile.in
|
||||||
bindir = "$bindir"
|
bindir = "$bindir"
|
||||||
libdir = "$libdir"
|
libdir = "$libdir"
|
||||||
|
|||||||
@ -1,47 +1,27 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
getConfig() {
|
getConfig() {
|
||||||
# Read the config file line by line; 'key' gets the first word, 'value'
|
|
||||||
# the rest
|
|
||||||
while read key value
|
while read key value
|
||||||
do
|
do
|
||||||
case $key in
|
case $key in
|
||||||
'#'*)
|
'#'*)
|
||||||
#comment - skip comment lines
|
#comment
|
||||||
;;
|
;;
|
||||||
'')
|
'')
|
||||||
#empty line - skip blank lines
|
#empty line
|
||||||
;;
|
;;
|
||||||
'[general]')
|
'[general]')
|
||||||
# Switch parsing context to the [general] section
|
|
||||||
context=General
|
context=General
|
||||||
;;
|
;;
|
||||||
'[source]')
|
'[source]')
|
||||||
# Switch parsing context to the [source] section
|
|
||||||
context=Source
|
context=Source
|
||||||
;;
|
;;
|
||||||
\[*\])
|
\[*\])
|
||||||
# Any other [section] header is a destination name
|
|
||||||
context=Destination
|
context=Destination
|
||||||
destination="${key#[}"
|
destination="${key#[}"
|
||||||
destination="${destination%]}"
|
destination="${destination%]}"
|
||||||
destinations+=("${destination%]}") # Append to list of destinations
|
destinations+=("${destination%]}")
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Dispatch key=value to the handler for the current section context
|
|
||||||
getConfig$context
|
getConfig$context
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -1,50 +1,30 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
getConfigDestination() {
|
getConfigDestination() {
|
||||||
case "$key" in
|
case "$key" in
|
||||||
'enabled')
|
'enabled')
|
||||||
# 1 = process this destination, 0 = skip it
|
|
||||||
destinationenabled["$destination"]="$value"
|
destinationenabled["$destination"]="$value"
|
||||||
;;
|
;;
|
||||||
'path')
|
'path')
|
||||||
# Strip trailing slash for consistency
|
|
||||||
destinationpath["$destination"]="${value%/}"
|
destinationpath["$destination"]="${value%/}"
|
||||||
;;
|
;;
|
||||||
'format')
|
'format')
|
||||||
case "$value" in
|
case "$value" in
|
||||||
'mp3')
|
'mp3')
|
||||||
destinationformat["$destination"]=mp3
|
destinationformat["$destination"]=mp3
|
||||||
# Flag that lame must be present
|
|
||||||
lameneeded=1
|
lameneeded=1
|
||||||
# MP3 can't handle more than 2 channels
|
# MP3 can't handfle more than 2 channels
|
||||||
[[ -z ${destinationchannels["$destination"]} ]] \
|
[[ -z ${destinationchannels["$destination"]} ]] \
|
||||||
&& destinationchannels["$destination"]=2
|
&& destinationchannels["$destination"]=2
|
||||||
;;
|
;;
|
||||||
'opus')
|
'opus')
|
||||||
destinationformat["$destination"]=opus
|
destinationformat["$destination"]=opus
|
||||||
# Flag that opusenc must be present
|
|
||||||
opusencneeded=1
|
opusencneeded=1
|
||||||
;;
|
;;
|
||||||
'vorbis')
|
'vorbis')
|
||||||
destinationformat["$destination"]=vorbis
|
destinationformat["$destination"]=vorbis
|
||||||
# Flag that oggenc must be present
|
|
||||||
oggencneeded=1
|
oggencneeded=1
|
||||||
;;
|
;;
|
||||||
'copy')
|
'copy')
|
||||||
# Files are copied/hardlinked as-is
|
|
||||||
destinationformat["$destination"]=copy
|
destinationformat["$destination"]=copy
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@ -54,7 +34,6 @@ getConfigDestination() {
|
|||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'quality')
|
'quality')
|
||||||
# Vorbis-only: oggenc quality scale (integer, typically 0-10)
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if ! [[ $value =~ $expr ]]
|
if ! [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -67,32 +46,27 @@ getConfigDestination() {
|
|||||||
destinationquality["$destination"]="$value"
|
destinationquality["$destination"]="$value"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid parameter '$key' for" \
|
echo "Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
||||||
"format" \
|
|
||||||
"'${destinationformat["$destination"]}'" >&2
|
|
||||||
exit $EFMTINVPARM
|
exit $EFMTINVPARM
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'normalize')
|
'normalize')
|
||||||
# Whether to normalize audio volume (via sox --norm)
|
|
||||||
case $value in
|
case $value in
|
||||||
'true'|'on'|'yes'|'1')
|
'true'|'on'|'yes')
|
||||||
destinationnormalize["$destination"]=1
|
destinationnormalize["$destination"]=1
|
||||||
;;
|
;;
|
||||||
'false'|'off'|'no'|'0')
|
'false'|'off'|'no')
|
||||||
destinationnormalize["$destination"]=0
|
destinationnormalize["$destination"]=0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "normalize takes values:" \
|
echo "normalize takes values:" \
|
||||||
"'yes' ,'true' ,'on', '1', 'no'," \
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
||||||
"'false','off', '0'"
|
"'off'"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'bitrate')
|
'bitrate')
|
||||||
# Opus/MP3: target bitrate in kbps
|
|
||||||
# integer only; Bash doesn't support floats
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if ! [[ $value =~ $expr ]]
|
if ! [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -114,7 +88,6 @@ getConfigDestination() {
|
|||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'loss')
|
'loss')
|
||||||
# Opus Forward Error Correction: expected packet loss percentage (0-100)
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if ! [[ $value =~ $expr ]]
|
if ! [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -127,15 +100,12 @@ getConfigDestination() {
|
|||||||
destinationloss["$destination"]="$value"
|
destinationloss["$destination"]="$value"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid parameter '$key' for" \
|
echo "$Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
||||||
"format" \
|
|
||||||
"'${destinationformat["$destination"]}'" >&2
|
|
||||||
exit $EFMTINVPARM
|
exit $EFMTINVPARM
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'channels')
|
'channels')
|
||||||
# Up/downmix to this many channels if needed
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if ! [[ $value =~ $expr ]]
|
if ! [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -146,7 +116,6 @@ getConfigDestination() {
|
|||||||
destinationchannels["$destination"]=$value
|
destinationchannels["$destination"]=$value
|
||||||
;;
|
;;
|
||||||
'frequency')
|
'frequency')
|
||||||
# Resample to this sample rate in Hz (e.g. 44100)
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if ! [[ $value =~ $expr ]]
|
if ! [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -157,24 +126,21 @@ getConfigDestination() {
|
|||||||
destinationfrequency["$destination"]=$value
|
destinationfrequency["$destination"]=$value
|
||||||
;;
|
;;
|
||||||
'noresample')
|
'noresample')
|
||||||
# MP3-only: prevent lame from auto-downsampling at low
|
|
||||||
# bitrates
|
|
||||||
case $value in
|
case $value in
|
||||||
'true'|'on'|'yes'|'1')
|
'true'|'on'|'yes')
|
||||||
destinationnoresample["$destination"]=1
|
destinationnoresample["$destination"]=1
|
||||||
;;
|
;;
|
||||||
'false'|'off'|'no'|'0')
|
'false'|'off'|'no')
|
||||||
destinationnoresample["$destination"]=0
|
destinationnoresample["$destination"]=0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "noresample takes values:" \
|
echo "noresample takes values:" \
|
||||||
"'yes' ,'true' ,'on', '1', 'no',"\
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
||||||
"'false','off', '0'"
|
"'off'"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'rename')
|
'rename')
|
||||||
# File rename pattern using %{tag} tokens
|
|
||||||
case "$value" in
|
case "$value" in
|
||||||
*/*)
|
*/*)
|
||||||
destinationrenamepath["$destination"]="${value%/*}"
|
destinationrenamepath["$destination"]="${value%/*}"
|
||||||
@ -183,62 +149,46 @@ getConfigDestination() {
|
|||||||
destinationrename["$destination"]="${value##*/}"
|
destinationrename["$destination"]="${value##*/}"
|
||||||
;;
|
;;
|
||||||
'fat32compat')
|
'fat32compat')
|
||||||
# Strip FAT32-illegal characters (? \ < > : * | ")
|
|
||||||
# trim spaces/dots
|
|
||||||
case $value in
|
case $value in
|
||||||
'true'|'on'|'yes'|'1')
|
'true'|'on'|'yes')
|
||||||
destinationfat32compat["$destination"]=1
|
destinationfat32compat["$destination"]=1
|
||||||
;;
|
;;
|
||||||
'false'|'off'|'no'|'0')
|
'false'|'off'|'no')
|
||||||
destinationfat32compat["$destination"]=0
|
destinationfat32compat["$destination"]=0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "fat32compat takes values:" \
|
echo "fat32compat takes values:" \
|
||||||
"'yes' ,'true' ,'on', '1', 'no',"\
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
||||||
"'false','off', '0'"
|
"'off'"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'ascii-only')
|
'ascii-only')
|
||||||
# Transliterate Unicode filenames to ASCII using
|
|
||||||
#Requires Perl Text::Unidecode
|
|
||||||
case $value in
|
case $value in
|
||||||
'true'|'on'|'yes'|'1')
|
'true'|'on'|'yes')
|
||||||
destinationascii["$destination"]=1
|
destinationascii["$destination"]=1
|
||||||
# Signal that the perl coprocess will
|
|
||||||
# be needed
|
|
||||||
textunidecodeneeded=1
|
textunidecodeneeded=1
|
||||||
;;
|
;;
|
||||||
'false'|'off'|'no'|'0')
|
'false'|'off'|'no')
|
||||||
destinationascii["$destination"]=0
|
destinationascii["$destination"]=0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "ascii-only takes values:" \
|
echo "ascii-only takes values:" \
|
||||||
"'yes' ,'true' ,'on', '1', 'no',"\
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
||||||
"'false','off', '0'"
|
"'off'"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
'skip_mime-type')
|
'skip_mime-type')
|
||||||
# Accumulate pipe-separated list of mime patterns to
|
|
||||||
# exclude entirely
|
|
||||||
destinationskipmime[$destination]="${destinationskipmime[$destination]:+${destinationskipmime[$destination]}|}$value"
|
destinationskipmime[$destination]="${destinationskipmime[$destination]:+${destinationskipmime[$destination]}|}$value"
|
||||||
;;
|
;;
|
||||||
'copy_mime-type')
|
'copy_mime-type')
|
||||||
# Accumulate pipe-separated list of mime patterns to
|
|
||||||
# copy verbatim (action=2)
|
|
||||||
destinationcopymime[$destination]="${destinationcopymime[$destination]:+${destinationcopymime[$destination]}|}$value"
|
destinationcopymime[$destination]="${destinationcopymime[$destination]:+${destinationcopymime[$destination]}|}$value"
|
||||||
;;
|
;;
|
||||||
'copy_extension')
|
'copy_extension')
|
||||||
# Accumulate pipe-separated list of file extensions to
|
|
||||||
# copy verbatim
|
|
||||||
destinationcopyext[$destination]="${destinationcopyext[$destination]:+${destinationcopyext[$destination]}|}$value"
|
destinationcopyext[$destination]="${destinationcopyext[$destination]:+${destinationcopyext[$destination]}|}$value"
|
||||||
;;
|
;;
|
||||||
'higher-than')
|
'higher-than')
|
||||||
# Only re-encode source files with bitrate ABOVE this
|
|
||||||
# threshold (kbps)
|
|
||||||
# Files at or below this bitrate will be
|
|
||||||
# hardlinked/copied instead
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if ! [[ $value =~ $expr ]]
|
if ! [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
|
|||||||
@ -1,23 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
getConfigGeneral() {
|
getConfigGeneral() {
|
||||||
case $key in
|
case $key in
|
||||||
'max-load')
|
'max-load')
|
||||||
# Target system 1-minute load average
|
|
||||||
# concurrency is adjusted to stay near this
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if [[ $value =~ $expr ]]
|
if [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -29,7 +13,6 @@ getConfigGeneral() {
|
|||||||
unset expr
|
unset expr
|
||||||
;;
|
;;
|
||||||
'load-interval')
|
'load-interval')
|
||||||
# How often (seconds) to adjust worker count
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
if [[ $value =~ $expr ]]
|
if [[ $value =~ $expr ]]
|
||||||
then
|
then
|
||||||
@ -65,7 +48,6 @@ getConfigGeneral() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
# Best-effort class; niceness 0 (highest) to 7 (lowest)
|
|
||||||
if [ -n "$niceness" ] \
|
if [ -n "$niceness" ] \
|
||||||
&& (( niceness >= 0 && niceness <= 7 ))
|
&& (( niceness >= 0 && niceness <= 7 ))
|
||||||
then
|
then
|
||||||
@ -77,11 +59,10 @@ getConfigGeneral() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
# Idle class: only gets I/O when no other process needs it
|
|
||||||
ionice="ionice -c3 "
|
ionice="ionice -c3 "
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid ionice class $value"\
|
echo "Invalid ionice parameters $value"\
|
||||||
>&2
|
>&2
|
||||||
exit $EIONICE
|
exit $EIONICE
|
||||||
;;
|
;;
|
||||||
@ -89,23 +70,15 @@ getConfigGeneral() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
'temporary-directory')
|
'temporary-directory')
|
||||||
# Directory for
|
|
||||||
# * SQLite FIFOs
|
|
||||||
# * intermediate WAV files
|
|
||||||
# * debug logs
|
|
||||||
tempdir="$value"
|
tempdir="$value"
|
||||||
;;
|
;;
|
||||||
'database')
|
'database')
|
||||||
# Path to the SQLite database file
|
|
||||||
database="$value"
|
database="$value"
|
||||||
;;
|
;;
|
||||||
'skip-timestamp-microsec')
|
'skip-timestamp-microsec')
|
||||||
# If non-zero, ignore sub-second precision in file timestamps
|
|
||||||
# Useful on filesystems that don't preserve microseconds
|
|
||||||
skip_us_timestamp="$value"
|
skip_us_timestamp="$value"
|
||||||
;;
|
;;
|
||||||
debug)
|
debug)
|
||||||
# Allow config file to raise debug level (but not lower it)
|
|
||||||
(( value > debug )) && debug=$value
|
(( value > debug )) && debug=$value
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -1,27 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
getConfigSource() {
|
getConfigSource() {
|
||||||
case "$key" in
|
case "$key" in
|
||||||
'path')
|
'path')
|
||||||
# Root directory of music collection to transcode from
|
|
||||||
sourcepath="$value"
|
sourcepath="$value"
|
||||||
;;
|
;;
|
||||||
'skip')
|
'skip')
|
||||||
# Directory pattern to exclude from scanning
|
|
||||||
# Multiple 'skip' entries accumulate into this array
|
|
||||||
skippeddirectories+=( "$value" )
|
skippeddirectories+=( "$value" )
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -1,22 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
printConfig() {
|
printConfig() {
|
||||||
{
|
{
|
||||||
# Build a pipe-delimited table
|
|
||||||
# 'column -t -s|' will transform into columns
|
|
||||||
echo "General|Config file|$cffile"
|
echo "General|Config file|$cffile"
|
||||||
[ -n "$ionice" ] && echo "|IO Nice|$ionice"
|
[ -n "$ionice" ] && echo "|IO Nice|$ionice"
|
||||||
cat <<-EOF
|
cat <<-EOF
|
||||||
@ -28,8 +12,6 @@ printConfig() {
|
|||||||
|
|
||||||
Source|Path|$sourcepath
|
Source|Path|$sourcepath
|
||||||
EOF
|
EOF
|
||||||
# Print skipped directories
|
|
||||||
# use a continuation prefix after the first
|
|
||||||
for prune_expression in "${skippeddirectories[@]}"
|
for prune_expression in "${skippeddirectories[@]}"
|
||||||
do
|
do
|
||||||
(( printed )) \
|
(( printed )) \
|
||||||
@ -39,8 +21,6 @@ printConfig() {
|
|||||||
printed=1
|
printed=1
|
||||||
done
|
done
|
||||||
unset printed
|
unset printed
|
||||||
# Loop over destinations and print their settings
|
|
||||||
# use the destination name as a key into the associative arrays
|
|
||||||
for destination in ${destinations[@]}
|
for destination in ${destinations[@]}
|
||||||
do
|
do
|
||||||
cat <<-EOF
|
cat <<-EOF
|
||||||
@ -50,7 +30,6 @@ printConfig() {
|
|||||||
|Format|${destinationformat["$destination"]}
|
|Format|${destinationformat["$destination"]}
|
||||||
|Quality|${destinationquality["$destination"]}
|
|Quality|${destinationquality["$destination"]}
|
||||||
EOF
|
EOF
|
||||||
# Show format-specific fields
|
|
||||||
if [[ ${destinationformat["$destination"]} == opus ]]
|
if [[ ${destinationformat["$destination"]} == opus ]]
|
||||||
then
|
then
|
||||||
echo " |Expected loss|${destinationloss["$destination"]}"
|
echo " |Expected loss|${destinationloss["$destination"]}"
|
||||||
@ -68,7 +47,6 @@ printConfig() {
|
|||||||
|Path Change|${destinationrenamepath["$destination"]}
|
|Path Change|${destinationrenamepath["$destination"]}
|
||||||
|File Rename|${destinationrename["$destination"]}
|
|File Rename|${destinationrename["$destination"]}
|
||||||
EOF
|
EOF
|
||||||
# Display pipe-separated mime lists: one entry per row
|
|
||||||
[ -n "${destinationskipmime["$destination"]}" ] \
|
[ -n "${destinationskipmime["$destination"]}" ] \
|
||||||
&& echo " |Skipped mime-types|${destinationskipmime["$destination"]//\|/
|
&& echo " |Skipped mime-types|${destinationskipmime["$destination"]//\|/
|
||||||
| |}"
|
| |}"
|
||||||
@ -79,5 +57,5 @@ printConfig() {
|
|||||||
&& echo " |Copied extensions|${destinationcopyext["$destination"]//\|/
|
&& echo " |Copied extensions|${destinationcopyext["$destination"]//\|/
|
||||||
| |}"
|
| |}"
|
||||||
done
|
done
|
||||||
}|column -t -s'|' # Format as aligned columns using '|' as delimiter
|
}|column -t -s'|'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
|
|
||||||
# writeConfig() generates a new atom.cfg from the current in-memory settings.
|
|
||||||
# Output is sent to stdout so callers can redirect it to any config file path.
|
|
||||||
# Each setting is annotated with a description.
|
|
||||||
writeConfig() {
|
writeConfig() {
|
||||||
cat <<-EOCfg
|
cat <<-EOCfg
|
||||||
[general]
|
[general]
|
||||||
@ -64,7 +47,6 @@ path $sourcepath
|
|||||||
# * skip <directory>: String. Files in <directory> will be ignored. Note that
|
# * skip <directory>: String. Files in <directory> will be ignored. Note that
|
||||||
# <directory> can be any expression accepted by find.
|
# <directory> can be any expression accepted by find.
|
||||||
EOCfg
|
EOCfg
|
||||||
# Emit one skip line per skipped directory
|
|
||||||
for dir in "${skippeddirectories[@]}"
|
for dir in "${skippeddirectories[@]}"
|
||||||
do
|
do
|
||||||
echo $'skip\t\t\t'"$dir"
|
echo $'skip\t\t\t'"$dir"
|
||||||
@ -73,7 +55,6 @@ path $sourcepath
|
|||||||
|
|
||||||
|
|
||||||
EOCfg
|
EOCfg
|
||||||
# Emit one section per configured destination
|
|
||||||
for destination in "${destinations[@]}"
|
for destination in "${destinations[@]}"
|
||||||
do
|
do
|
||||||
cat <<-EOCfg
|
cat <<-EOCfg
|
||||||
@ -209,8 +190,6 @@ bitrate ${destinationquality["$destination"]}
|
|||||||
# be included in that destination. For more than one mime-type, use multiple
|
# be included in that destination. For more than one mime-type, use multiple
|
||||||
# times, as needed. The '*' character is a wildcard.
|
# times, as needed. The '*' character is a wildcard.
|
||||||
EOCfg
|
EOCfg
|
||||||
# Emit one skip_mime-type line per MIME pattern (pipe-separated
|
|
||||||
# in the array)
|
|
||||||
destinationskipmime["$destination"]="${destinationskipmime["$destination"]}|"
|
destinationskipmime["$destination"]="${destinationskipmime["$destination"]}|"
|
||||||
while [[ ${destinationskipmime["$destination"]} =~ \| ]]
|
while [[ ${destinationskipmime["$destination"]} =~ \| ]]
|
||||||
do
|
do
|
||||||
@ -224,7 +203,6 @@ bitrate ${destinationquality["$destination"]}
|
|||||||
# covers and other images to the destination. In fact, AtOM will try to use
|
# covers and other images to the destination. In fact, AtOM will try to use
|
||||||
# hard links instead of copies.
|
# hard links instead of copies.
|
||||||
EOCfg
|
EOCfg
|
||||||
# Emit one copy_mime-type line per MIME pattern
|
|
||||||
destinationcopymime["$destination"]="${destinationcopymime["$destination"]}|"
|
destinationcopymime["$destination"]="${destinationcopymime["$destination"]}|"
|
||||||
while [[ ${destinationcopymime["$destination"]} =~ \| ]]
|
while [[ ${destinationcopymime["$destination"]} =~ \| ]]
|
||||||
do
|
do
|
||||||
@ -235,7 +213,6 @@ bitrate ${destinationquality["$destination"]}
|
|||||||
|
|
||||||
# * copy_extension <extension>: Copy files whose name and with ".<extension>"
|
# * copy_extension <extension>: Copy files whose name and with ".<extension>"
|
||||||
EOCfg
|
EOCfg
|
||||||
# Emit one copy_extension line per extension pattern
|
|
||||||
destinationcopyext["$destination"]="${destinationcopyext["$destination"]}|"
|
destinationcopyext["$destination"]="${destinationcopyext["$destination"]}|"
|
||||||
while [[ ${destinationcopyext["$destination"]} =~ \| ]]
|
while [[ ${destinationcopyext["$destination"]} =~ \| ]]
|
||||||
do
|
do
|
||||||
|
|||||||
@ -1,25 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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() {
|
copyFiles_action() {
|
||||||
(( cron )) || echo -n $'Copying files...\033[K'
|
(( 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 '
|
echo '
|
||||||
SELECT
|
SELECT
|
||||||
source_files.filename,
|
source_files.filename,
|
||||||
@ -41,8 +22,6 @@ copyFiles_action() {
|
|||||||
AND mime_type_actions.action = 2;
|
AND mime_type_actions.action = 2;
|
||||||
|
|
||||||
SELECT "AtOM:NoMoreFiles";' >&3
|
SELECT "AtOM:NoMoreFiles";' >&3
|
||||||
# Results are NUL-delimited rows; sentinel value signals end of result
|
|
||||||
# set.
|
|
||||||
read -u4 -r -d $'\0' line
|
read -u4 -r -d $'\0' line
|
||||||
while ! [[ $line = AtOM:NoMoreFiles ]]
|
while ! [[ $line = AtOM:NoMoreFiles ]]
|
||||||
do
|
do
|
||||||
@ -50,14 +29,9 @@ copyFiles_action() {
|
|||||||
read -u4 -r -d $'\0' line
|
read -u4 -r -d $'\0' line
|
||||||
done
|
done
|
||||||
|
|
||||||
# Wrap all DB updates in a single transaction for performance.
|
|
||||||
echo 'BEGIN TRANSACTION;' >&3
|
echo 'BEGIN TRANSACTION;' >&3
|
||||||
for copyfile in "${copyfiles[@]}"
|
for copyfile in "${copyfiles[@]}"
|
||||||
do
|
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::*}
|
sourcefilename=${copyfile%%::AtOM:SQL:Sep::*}
|
||||||
sourcedir=${sourcefilename%/*}
|
sourcedir=${sourcefilename%/*}
|
||||||
rest="${copyfile#*::AtOM:SQL:Sep::}::AtOM:SQL:Sep::"
|
rest="${copyfile#*::AtOM:SQL:Sep::}::AtOM:SQL:Sep::"
|
||||||
@ -71,33 +45,20 @@ copyFiles_action() {
|
|||||||
rest=${rest#*::AtOM:SQL:Sep::}
|
rest=${rest#*::AtOM:SQL:Sep::}
|
||||||
(( count++ ))
|
(( count++ ))
|
||||||
(( cron )) || printf '\b\b\b\b%3i%%' $(( (count * 100) / ${#copyfiles[@]} ))
|
(( 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"]}" ]
|
if [ -n "${destinationrenamepath["$destination"]}" ]
|
||||||
then
|
then
|
||||||
destdir="$(guessPath)"
|
destdir="$(guessPath)"
|
||||||
guessstatus=$?
|
guessstatus=$?
|
||||||
case $guessstatus in
|
case $guessstatus in
|
||||||
1)
|
1)
|
||||||
# guessPath found no transcoded
|
|
||||||
# sibling; skip this file entirely.
|
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
# Transcoded siblings exist but are not
|
|
||||||
# yet up to date; defer this copy until
|
|
||||||
# the next run so the directory is
|
|
||||||
# stable.
|
|
||||||
(( postponed++ ))
|
(( postponed++ ))
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
# No rename pattern: mirror the source directory
|
|
||||||
# structure under the destination root, sanitizing each
|
|
||||||
# path component for the target FS.
|
|
||||||
destdir="${destinationpath["$destination"]}/"
|
destdir="${destinationpath["$destination"]}/"
|
||||||
if [[ $sourcefilename =~ / ]]
|
if [[ $sourcefilename =~ / ]]
|
||||||
then
|
then
|
||||||
@ -118,9 +79,6 @@ copyFiles_action() {
|
|||||||
fi
|
fi
|
||||||
fi
|
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 \
|
if cp -a --reflink=always \
|
||||||
"$sourcepath/$sourcefilename" \
|
"$sourcepath/$sourcefilename" \
|
||||||
"$destdir" \
|
"$destdir" \
|
||||||
@ -133,9 +91,6 @@ copyFiles_action() {
|
|||||||
"$sourcepath/$sourcefilename" \
|
"$sourcepath/$sourcefilename" \
|
||||||
"$destdir"
|
"$destdir"
|
||||||
then
|
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::}
|
destfilename=${sourcefilename//$'\n'/::AtOM:NewLine:SQL:Inline::}
|
||||||
Update destination_files \
|
Update destination_files \
|
||||||
filename \
|
filename \
|
||||||
|
|||||||
@ -1,39 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/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() {
|
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]}" ] \
|
[ -z "${destinationfrequency[$destination]}" ] \
|
||||||
|| (( ${rate:-0} == ${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]}" ] \
|
[ -z "${destinationchannels[$destination]}" ] \
|
||||||
|| (( ${channels:-0} == ${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]} )) \
|
(( ${bitrate:-1000} == ${destinationquality[$destination]} )) \
|
||||||
|| (
|
|| (
|
||||||
[ -n "${destinationmaxbps[$destination]}" ] \
|
[ -n "${destinationmaxbps[$destination]}" ] \
|
||||||
|
|||||||
@ -1,29 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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() {
|
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( (
|
echo 'SELECT IFNULL( (
|
||||||
SELECT destination_files.last_change
|
SELECT destination_files.last_change
|
||||||
FROM destination_files
|
FROM destination_files
|
||||||
@ -45,17 +22,10 @@ guessPath() {
|
|||||||
),"0.0");
|
),"0.0");
|
||||||
'>&3
|
'>&3
|
||||||
read -u4 -r -d $'\0' timestamp
|
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 ))
|
if (( ${timestamp/./} == 0 ))
|
||||||
then
|
then
|
||||||
# No transcoded sibling found at all
|
|
||||||
# caller should postpone this copy.
|
|
||||||
return 2
|
return 2
|
||||||
fi
|
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( (
|
echo 'SELECT IFNULL( (
|
||||||
SELECT destination_files.filename
|
SELECT destination_files.filename
|
||||||
FROM destination_files
|
FROM destination_files
|
||||||
@ -79,12 +49,8 @@ guessPath() {
|
|||||||
read -u4 -r -d $'\0' filename
|
read -u4 -r -d $'\0' filename
|
||||||
if [[ $filename != AtOM:NotFound ]]
|
if [[ $filename != AtOM:NotFound ]]
|
||||||
then
|
then
|
||||||
# Strip the filename component to return only the directory
|
|
||||||
# portion.
|
|
||||||
echo "${filename%/*}"
|
echo "${filename%/*}"
|
||||||
else
|
else
|
||||||
# Sibling record exists but has no usable filename — skip this
|
|
||||||
# file.
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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() {
|
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##*.}"
|
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 \
|
if \
|
||||||
cp -al \
|
cp -al \
|
||||||
"$sourcepath/$filename" \
|
"$sourcepath/$filename" \
|
||||||
@ -29,11 +10,6 @@ copyFiles_matching() {
|
|||||||
"$sourcepath/$filename" \
|
"$sourcepath/$filename" \
|
||||||
"${destinationpath[$destination]}/$destdir/$destfile.$extension"
|
"${destinationpath[$destination]}/$destdir/$destfile.$extension"
|
||||||
then
|
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 \
|
echo \
|
||||||
"UPDATE destination_files" \
|
"UPDATE destination_files" \
|
||||||
"SET filename=" \
|
"SET filename=" \
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
Delete() {
|
Delete() {
|
||||||
#Delete table < where_key where_operator where_value
|
#Delete table < where_key where_operator where_value
|
||||||
# [where_key where_operator where_value
|
# [where_key where_operator where_value
|
||||||
@ -24,16 +10,13 @@ Delete() {
|
|||||||
value \
|
value \
|
||||||
where_statement \
|
where_statement \
|
||||||
results
|
results
|
||||||
# Build WHERE clause from stdin: one "key op value" triple per line
|
|
||||||
while read key operator value
|
while read key operator value
|
||||||
do
|
do
|
||||||
(( ${#where_statement} )) && where_statement+=( "AND" )
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
||||||
if [[ $value == NULL ]]
|
if [[ $value == NULL ]]
|
||||||
then
|
then
|
||||||
# NULL comparisons require IS NULL, not = "NULL"
|
|
||||||
where_statement+=( "$key is NULL" )
|
where_statement+=( "$key is NULL" )
|
||||||
else
|
else
|
||||||
# Double embedded quotes to safely escape string values
|
|
||||||
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@ -1,32 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
Insert() {
|
Insert() {
|
||||||
#Insert table [no_id] < key value
|
#Insert table [no_id] < key value
|
||||||
# [key value
|
# [key value
|
||||||
# […]]
|
# […]]
|
||||||
#
|
|
||||||
# If no_id is set to a non-zero value, the function will not return the
|
|
||||||
# auto-assigned row ID of the inserted row.
|
|
||||||
local \
|
local \
|
||||||
table="$1" \
|
table="$1" \
|
||||||
no_id="${2:-0}" \
|
no_id="${2:-0}" \
|
||||||
insert_keys \
|
insert_keys \
|
||||||
insert_values \
|
insert_values \
|
||||||
results
|
results
|
||||||
# Build column list and value list from stdin key-value pairs
|
|
||||||
while read key value
|
while read key value
|
||||||
do
|
do
|
||||||
(( ${#insert_keys} )) && insert_keys+=","
|
(( ${#insert_keys} )) && insert_keys+=","
|
||||||
@ -34,24 +16,16 @@ Insert() {
|
|||||||
(( ${#insert_values} )) && insert_values+=","
|
(( ${#insert_values} )) && insert_values+=","
|
||||||
case $value in
|
case $value in
|
||||||
'::AtOM:FT::'*)
|
'::AtOM:FT::'*)
|
||||||
# Force-text prefix: strip the marker and quote
|
|
||||||
# as string (prevents numeric-looking values
|
|
||||||
# from being stored as int / float)
|
|
||||||
value="${value//::AtOM:FT::/}"
|
value="${value//::AtOM:FT::/}"
|
||||||
insert_values+='"'"${value//\"/\"\"}"'"'
|
insert_values+='"'"${value//\"/\"\"}"'"'
|
||||||
;;
|
;;
|
||||||
'NULL')
|
'NULL')
|
||||||
# Insert SQL NULL (not the string "NULL")
|
|
||||||
insert_values+="NULL"
|
insert_values+="NULL"
|
||||||
;;
|
;;
|
||||||
+([0-9])?(.+([0-9])))
|
+([0-9])?(.+([0-9])))
|
||||||
# Pure integer or decimal: insert unquoted for
|
|
||||||
# numeric storage
|
|
||||||
insert_values+=$value
|
insert_values+=$value
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# General string: restore encoded newlines,
|
|
||||||
# then quote
|
|
||||||
value=${value//::AtOM:NewLine:SQL:Inline::/$'\n'}
|
value=${value//::AtOM:NewLine:SQL:Inline::/$'\n'}
|
||||||
insert_values+='"'"${value//\"/\"\"}"'"'
|
insert_values+='"'"${value//\"/\"\"}"'"'
|
||||||
;;
|
;;
|
||||||
@ -61,7 +35,6 @@ Insert() {
|
|||||||
"( $insert_keys )" \
|
"( $insert_keys )" \
|
||||||
"VALUES" \
|
"VALUES" \
|
||||||
"( $insert_values );" >&3
|
"( $insert_values );" >&3
|
||||||
# Unless no_id is set, return the auto-assigned row ID
|
|
||||||
(( no_id )) || {
|
(( no_id )) || {
|
||||||
echo 'SELECT LAST_INSERT_ROWID();' >&3
|
echo 'SELECT LAST_INSERT_ROWID();' >&3
|
||||||
read -u4 -r -d $'\0' results
|
read -u4 -r -d $'\0' results
|
||||||
|
|||||||
@ -1,23 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
InsertIfUnset() {
|
InsertIfUnset() {
|
||||||
#InsertIfUnset table [no_id] < key value \n key value
|
#InsertIfUnset table [no_id] < key value \n key value
|
||||||
#
|
|
||||||
# If no_id is set to a non-zero value, the function will not return the
|
|
||||||
# auto-assigned row ID of the inserted row.
|
|
||||||
local \
|
local \
|
||||||
table="$1" \
|
table="$1" \
|
||||||
no_id="${2:-0}" \
|
no_id="${2:-0}" \
|
||||||
@ -27,32 +10,27 @@ InsertIfUnset() {
|
|||||||
results \
|
results \
|
||||||
value \
|
value \
|
||||||
values
|
values
|
||||||
# Read all key-value pairs from stdin into parallel arrays
|
|
||||||
while read key value
|
while read key value
|
||||||
do
|
do
|
||||||
keys+=( "$key" )
|
keys+=( "$key" )
|
||||||
values+=( "$value" )
|
values+=( "$value" )
|
||||||
done
|
done
|
||||||
# Choose which column to return: first key column if no_id, else 'id'
|
|
||||||
if (( no_id ))
|
if (( no_id ))
|
||||||
then
|
then
|
||||||
column="${keys[0]}"
|
column="${keys[0]}"
|
||||||
else
|
else
|
||||||
column='id'
|
column='id'
|
||||||
fi
|
fi
|
||||||
# Check if a matching row already exists
|
|
||||||
if ! results=$(
|
if ! results=$(
|
||||||
Select "$table" "$column" < <(
|
Select "$table" "$column" < <(
|
||||||
for key in ${!keys[@]}
|
for key in ${!keys[@]}
|
||||||
do
|
do
|
||||||
# Strip ::AtOM:FT:: for WHERE comparison
|
|
||||||
echo "${keys[$key]}" = \
|
echo "${keys[$key]}" = \
|
||||||
"${values[$key]//::AtOM:FT::}"
|
"${values[$key]//::AtOM:FT::}"
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
# Row not found: insert it and return the new id
|
|
||||||
results=$(
|
results=$(
|
||||||
Insert "$table" < <(
|
Insert "$table" < <(
|
||||||
for key in ${!keys[@]}
|
for key in ${!keys[@]}
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
InsertOrUpdate() {
|
InsertOrUpdate() {
|
||||||
#InsertOrUpdate table set_key set_value [set_key set_value […]] < where_key where_value
|
#InsertOrUpdate table set_key set_value [set_key set_value […]] < where_key where_value
|
||||||
# [where_key where_value
|
# [where_key where_value
|
||||||
@ -29,8 +15,6 @@ InsertOrUpdate() {
|
|||||||
what \
|
what \
|
||||||
results
|
results
|
||||||
shift
|
shift
|
||||||
# Parse positional args as alternating key/value pairs for the SET
|
|
||||||
# clause
|
|
||||||
what=key
|
what=key
|
||||||
for argument
|
for argument
|
||||||
do
|
do
|
||||||
@ -45,13 +29,11 @@ InsertOrUpdate() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
# Read WHERE conditions from stdin
|
|
||||||
while read key value
|
while read key value
|
||||||
do
|
do
|
||||||
keys+=( "$key" )
|
keys+=( "$key" )
|
||||||
values+=( "$value" )
|
values+=( "$value" )
|
||||||
done
|
done
|
||||||
# Check if a matching row exists using the WHERE keys
|
|
||||||
if results=$(
|
if results=$(
|
||||||
Select "$table" ${keys[0]} < <(
|
Select "$table" ${keys[0]} < <(
|
||||||
for key in ${!keys[@]}
|
for key in ${!keys[@]}
|
||||||
@ -61,7 +43,6 @@ InsertOrUpdate() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
# Row exists: update it with the SET values
|
|
||||||
Update "$table" "$@" < <(
|
Update "$table" "$@" < <(
|
||||||
for key in ${!keys[@]}
|
for key in ${!keys[@]}
|
||||||
do
|
do
|
||||||
@ -69,8 +50,6 @@ InsertOrUpdate() {
|
|||||||
done
|
done
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
# Row not found: insert combining SET columns and WHERE-match
|
|
||||||
# columns
|
|
||||||
results=$(
|
results=$(
|
||||||
Insert "$table" < <(
|
Insert "$table" < <(
|
||||||
for key in ${!set_keys[@]}
|
for key in ${!set_keys[@]}
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
Select() {
|
Select() {
|
||||||
#Select table [col1 [col2 [..]]] < WHERE_key WHERE_operator WHERE_value
|
#Select table [col1 [col2 [..]]] < WHERE_key WHERE_operator WHERE_value
|
||||||
# [WHERE_key WHERE_operator WHERE_value
|
# [WHERE_key WHERE_operator WHERE_value
|
||||||
@ -25,28 +11,23 @@ Select() {
|
|||||||
results \
|
results \
|
||||||
where_statement
|
where_statement
|
||||||
shift
|
shift
|
||||||
# Build column list
|
|
||||||
for col
|
for col
|
||||||
do
|
do
|
||||||
(( ${#columns} )) && columns+=','
|
(( ${#columns} )) && columns+=','
|
||||||
columns+="$col"
|
columns+="$col"
|
||||||
done
|
done
|
||||||
# Build WHERE clause from stdin triplets
|
|
||||||
while read key operator value
|
while read key operator value
|
||||||
do
|
do
|
||||||
(( ${#where_statement} )) && where_statement+=( "AND" )
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
||||||
# Restore encoded newlines before embedding in SQL
|
|
||||||
value=${value//::AtOM:NewLine:SQL:Inline::/$'\n'}
|
value=${value//::AtOM:NewLine:SQL:Inline::/$'\n'}
|
||||||
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
||||||
done
|
done
|
||||||
# Use IFNULL so SQLite always produces output.
|
|
||||||
echo "SELECT IFNULL(" \
|
echo "SELECT IFNULL(" \
|
||||||
"(SELECT $columns FROM $table" \
|
"(SELECT $columns FROM $table" \
|
||||||
"WHERE ${where_statement[@]})" \
|
"WHERE ${where_statement[@]})" \
|
||||||
",'SQL::Select:not found'" \
|
",'SQL::Select:not found'" \
|
||||||
");" >&3
|
");" >&3
|
||||||
read -u 4 -r -d $'\0' results
|
read -u 4 -r -d $'\0' results
|
||||||
# Return exit code 1 if the sentinel value indicates no row was found
|
|
||||||
if ! [[ $results == "SQL::Select:not found" ]]
|
if ! [[ $results == "SQL::Select:not found" ]]
|
||||||
then
|
then
|
||||||
echo "$results"
|
echo "$results"
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
Update() {
|
Update() {
|
||||||
#Update table set_key set_value [set_key set_value […]] < where_key where_operator where_value
|
#Update table set_key set_value [set_key set_value […]] < where_key where_operator where_value
|
||||||
# [where_key where_operator where_value
|
# [where_key where_operator where_value
|
||||||
@ -30,22 +16,17 @@ Update() {
|
|||||||
where_statement \
|
where_statement \
|
||||||
results
|
results
|
||||||
shift
|
shift
|
||||||
# Parse positional args as alternating key/value for the SET clause
|
|
||||||
what=key
|
what=key
|
||||||
for argument
|
for argument
|
||||||
do
|
do
|
||||||
case $what in
|
case $what in
|
||||||
key)
|
key)
|
||||||
# Backtick-quote column name to handle reserved
|
|
||||||
# words
|
|
||||||
set_statement="${set_statement:+${set_statement},}\`$argument\`"
|
set_statement="${set_statement:+${set_statement},}\`$argument\`"
|
||||||
what=value
|
what=value
|
||||||
;;
|
;;
|
||||||
value)
|
value)
|
||||||
case $argument in
|
case $argument in
|
||||||
'::AtOM:FT::'*)
|
'::AtOM:FT::'*)
|
||||||
# Force-text: strip prefix and
|
|
||||||
# quote as string
|
|
||||||
argument="${argument//::AtOM:FT::/}"
|
argument="${argument//::AtOM:FT::/}"
|
||||||
set_statement+=" = "'"'"${argument//\"/\"\"}"'"'
|
set_statement+=" = "'"'"${argument//\"/\"\"}"'"'
|
||||||
;;
|
;;
|
||||||
@ -53,7 +34,6 @@ Update() {
|
|||||||
set_statement+=" = NULL"
|
set_statement+=" = NULL"
|
||||||
;;
|
;;
|
||||||
+([0-9])?(.+([0-9])))
|
+([0-9])?(.+([0-9])))
|
||||||
# Numeric value: store unquoted
|
|
||||||
set_statement+=" = $argument"
|
set_statement+=" = $argument"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@ -64,7 +44,6 @@ Update() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
# Build WHERE clause from stdin
|
|
||||||
while read key operator value
|
while read key operator value
|
||||||
do
|
do
|
||||||
(( ${#where_statement} )) && where_statement+=( "AND" )
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
||||||
@ -73,7 +52,6 @@ Update() {
|
|||||||
where_statement+=( "$key is NULL" )
|
where_statement+=( "$key is NULL" )
|
||||||
;;
|
;;
|
||||||
+([0-9.]))
|
+([0-9.]))
|
||||||
# Numeric: compare without quotes
|
|
||||||
where_statement+=( "$key $operator $value" )
|
where_statement+=( "$key $operator $value" )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@ -1,53 +1,26 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
# Current schema version this AtOM binary understands
|
|
||||||
currentdbversion=8
|
currentdbversion=8
|
||||||
checkDatabaseVersion() {
|
checkDatabaseVersion() {
|
||||||
local dbversion
|
local dbversion
|
||||||
# Try to read the stored version from the 'atom' metadata table
|
|
||||||
if dbversion=$(Select atom version <<<"\"1\" = 1")
|
if dbversion=$(Select atom version <<<"\"1\" = 1")
|
||||||
then
|
then
|
||||||
if (( dbversion == currentdbversion ))
|
if (( dbversion == currentdbversion ))
|
||||||
then
|
then
|
||||||
return 0 # Already up to date
|
return 0
|
||||||
elif (( dbversion < currentdbversion ))
|
elif (( dbversion < currentdbversion ))
|
||||||
then
|
then
|
||||||
# Run sequential upgrade functions until we reach
|
|
||||||
# `$currentdbversion`
|
|
||||||
until (( dbversion == currentdbversion ))
|
until (( dbversion == currentdbversion ))
|
||||||
do
|
do
|
||||||
# Dynamically calls e.g. upgradedatabase_3_4
|
|
||||||
upgradedatabase_${dbversion}_$((dbversion+1))
|
upgradedatabase_${dbversion}_$((dbversion+1))
|
||||||
# After each upgrade, re-read the version from
|
|
||||||
# the database to ensure it was updated
|
|
||||||
# correctly
|
|
||||||
dbversion=$(Select atom version <<<"\"1\" = 1")
|
dbversion=$(Select atom version <<<"\"1\" = 1")
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
# DB was created by a newer AtOM; we can't run and
|
|
||||||
# ensure consistency
|
|
||||||
echo "Database schema version $dbversion is" \
|
echo "Database schema version $dbversion is" \
|
||||||
"higher thanthat of this version of" \
|
"higher thanthat of this version of" \
|
||||||
"AtOM ($currentdbversion). Bailing out." >&2
|
"AtOM ($currentdbversion). Bailing out." >&2
|
||||||
exit $EDBVERSION
|
exit $EDBVERSION
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# No version row found: this is a database from very early
|
|
||||||
# drafts
|
|
||||||
# This is stupid but nobody is running with DB schema v0 anyway
|
|
||||||
Insert atom 1 <<<"version $currentdbversion"
|
Insert atom 1 <<<"version $currentdbversion"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
closeDatabase() {
|
closeDatabase() {
|
||||||
# Run VACUUM to reclaim space and defragment the database before closing
|
|
||||||
echo 'vacuum;' >&3
|
echo 'vacuum;' >&3
|
||||||
# Tell sqlite3 to exit cleanly
|
|
||||||
echo .quit >&3
|
echo .quit >&3
|
||||||
(( debug )) && echo -n "Waiting for SQLite to terminate... "
|
(( debug )) && echo -n "Waiting for SQLite to terminate... "
|
||||||
# Close the debug tee fd if it was opened (debug level > 2)
|
|
||||||
(( debug > 2 )) && exec 5>&-
|
(( debug > 2 )) && exec 5>&-
|
||||||
# Wait for the sqlite3 background process to fully exit
|
|
||||||
wait $db_pid
|
wait $db_pid
|
||||||
(( debug )) && echo OK
|
(( debug )) && echo OK
|
||||||
# Close the write end of the SQLite input FIFO (FD 3)
|
|
||||||
exec 3>&-
|
exec 3>&-
|
||||||
# Close the read end of the SQLite output FIFO (FD 4)
|
|
||||||
exec 4<&-
|
exec 4<&-
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
openDatabase() {
|
openDatabase() {
|
||||||
local \
|
local \
|
||||||
populate_db
|
populate_db
|
||||||
|
|
||||||
# If the DB file doesn't exist yet, mark it for schema population
|
|
||||||
[[ -f "$database" ]] || populate_db=1
|
[[ -f "$database" ]] || populate_db=1
|
||||||
|
|
||||||
# Create named FIFOs for bidirectional communication with sqlite3
|
|
||||||
rm -f "$tempdir"/sqlite.{in,out}
|
rm -f "$tempdir"/sqlite.{in,out}
|
||||||
mkfifo "$tempdir"/sqlite.{in,out}
|
mkfifo "$tempdir"/sqlite.{in,out}
|
||||||
|
|
||||||
# Start sqlite3 in background:
|
|
||||||
# - '-newline ::AtOM:SQL:EOL::' makes each row end with our custom
|
|
||||||
# marker. Allows storing newlines.
|
|
||||||
# - pipe through sed to convert the custom EOL to NUL bytes
|
|
||||||
# (for 'read -d $'\0'')
|
|
||||||
# - sed also removes the trailing newline that follows each NUL
|
|
||||||
stdbuf -o0 sqlite3 -bail \
|
stdbuf -o0 sqlite3 -bail \
|
||||||
-newline $'::AtOM:SQL:EOL::\n' \
|
-newline $'::AtOM:SQL:EOL::\n' \
|
||||||
"$database" \
|
"$database" \
|
||||||
@ -37,42 +13,21 @@ openDatabase() {
|
|||||||
| stdbuf -o0 \
|
| stdbuf -o0 \
|
||||||
sed 's/::AtOM:SQL:EOL::/\x0/g;s/\(\x0\)\xA/\1/g' \
|
sed 's/::AtOM:SQL:EOL::/\x0/g;s/\(\x0\)\xA/\1/g' \
|
||||||
> "$tempdir/sqlite.out" &
|
> "$tempdir/sqlite.out" &
|
||||||
# Store the PID of the background sqlite3 process so we can wait for it
|
|
||||||
# to exit
|
|
||||||
db_pid=$!
|
db_pid=$!
|
||||||
|
|
||||||
# Open FD 3 as the write end (send SQL commands to sqlite3)
|
|
||||||
exec 3> "$tempdir"/sqlite.in
|
exec 3> "$tempdir"/sqlite.in
|
||||||
# Open FD 4 as the read end (receive query results from sqlite3)
|
|
||||||
exec 4< "$tempdir"/sqlite.out
|
exec 4< "$tempdir"/sqlite.out
|
||||||
|
|
||||||
# FIFOs can be deleted immediately after opening; the fds keep them
|
|
||||||
# alive
|
|
||||||
rm "$tempdir"/sqlite.{in,out}
|
rm "$tempdir"/sqlite.{in,out}
|
||||||
|
|
||||||
# At debug level > 2, tee all SQL to a debug log file (FD 5 = original FD 3)
|
|
||||||
if (( debug > 2 ))
|
if (( debug > 2 ))
|
||||||
then
|
then
|
||||||
exec 5>&3
|
exec 5>&3
|
||||||
exec 3> >(tee -a "$tempdir/debug.log" >&5)
|
exec 3> >(tee -a "$tempdir/debug.log" >&5)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If new database, populate schema from the SQL schema file
|
|
||||||
(( populate_db )) && cat $schema >&3
|
(( populate_db )) && cat $schema >&3
|
||||||
|
|
||||||
# Configure sqlite3 output separator to match what we parse with ::AtOM:SQL:Sep::
|
|
||||||
echo '.separator ::AtOM:SQL:Sep::' >&3
|
echo '.separator ::AtOM:SQL:Sep::' >&3
|
||||||
# Enforce referential integrity
|
|
||||||
echo 'PRAGMA foreign_keys = ON;' >&3
|
echo 'PRAGMA foreign_keys = ON;' >&3
|
||||||
# Allow trigger chains
|
|
||||||
echo 'PRAGMA recursive_triggers = ON;' >&3
|
echo 'PRAGMA recursive_triggers = ON;' >&3
|
||||||
# Keep temp tables in memory
|
|
||||||
echo 'PRAGMA temp_store = 2;' >&3
|
echo 'PRAGMA temp_store = 2;' >&3
|
||||||
# We don't handle concurrent writes, lock the database for exclusive
|
|
||||||
# access to prevent corruption
|
|
||||||
echo 'PRAGMA locking_mode = EXCLUSIVE;' >&3
|
echo 'PRAGMA locking_mode = EXCLUSIVE;' >&3
|
||||||
|
|
||||||
# Drain the initial empty result sqlite3 sends on startup
|
|
||||||
read -u4 -r -d $'\0'
|
read -u4 -r -d $'\0'
|
||||||
unset REPLY
|
unset REPLY
|
||||||
checkDatabaseVersion
|
checkDatabaseVersion
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_1_2() {
|
upgradedatabase_1_2() {
|
||||||
local data \
|
local data \
|
||||||
datas \
|
datas \
|
||||||
@ -22,11 +9,8 @@ upgradedatabase_1_2() {
|
|||||||
fat32
|
fat32
|
||||||
echo "Upgrading database to version 2... (backup is $database.bak_v1)"
|
echo "Upgrading database to version 2... (backup is $database.bak_v1)"
|
||||||
cp "$database" "$database.bak_v1"
|
cp "$database" "$database.bak_v1"
|
||||||
# Add new columns to hold the fat32compat and ascii settings separately
|
|
||||||
echo 'ALTER TABLE destination_files ADD COLUMN fat32compat INTEGER;' >&3
|
echo 'ALTER TABLE destination_files ADD COLUMN fat32compat INTEGER;' >&3
|
||||||
echo 'ALTER TABLE destination_files ADD COLUMN ascii INTEGER;' >&3
|
echo 'ALTER TABLE destination_files ADD COLUMN ascii INTEGER;' >&3
|
||||||
# Read all existing destination_files rows to migrate rename_pattern format
|
|
||||||
# Old format embedded fat32compat after a colon: "pattern:fat32value"
|
|
||||||
echo '
|
echo '
|
||||||
SELECT id,
|
SELECT id,
|
||||||
rename_pattern
|
rename_pattern
|
||||||
@ -40,17 +24,14 @@ SELECT "AtOM::NoMoreData";' >&3
|
|||||||
datas+=( "$data" )
|
datas+=( "$data" )
|
||||||
read -u4 -r -d $'\0' data
|
read -u4 -r -d $'\0' data
|
||||||
done
|
done
|
||||||
# Ensure consistency by performing all updates in a single transaction
|
|
||||||
echo 'BEGIN TRANSACTION;' >&3
|
echo 'BEGIN TRANSACTION;' >&3
|
||||||
for data in "${datas[@]}"
|
for data in "${datas[@]}"
|
||||||
do
|
do
|
||||||
id="${data%%::AtOM:SQL:Sep::*}"
|
id="${data%%::AtOM:SQL:Sep::*}"
|
||||||
rename_pattern="${data#*::AtOM:SQL:Sep::}"
|
rename_pattern="${data#*::AtOM:SQL:Sep::}"
|
||||||
# Split "pattern:fat32" on the colon separator
|
|
||||||
IFS=':'
|
IFS=':'
|
||||||
read pattern fat32 <<<"$rename_pattern"
|
read pattern fat32 <<<"$rename_pattern"
|
||||||
IFS="$oldIFS"
|
IFS="$oldIFS"
|
||||||
# ASCII-only didn't exist in v1; default to off
|
|
||||||
Update destination_files \
|
Update destination_files \
|
||||||
rename_pattern "$pattern" \
|
rename_pattern "$pattern" \
|
||||||
fat32compat "$fat32" \
|
fat32compat "$fat32" \
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_2_3() {
|
upgradedatabase_2_3() {
|
||||||
local data \
|
local data \
|
||||||
datas \
|
datas \
|
||||||
@ -20,9 +7,7 @@ upgradedatabase_2_3() {
|
|||||||
destination
|
destination
|
||||||
echo "Upgrading database to version 3... (backup is $database.bak_v2)"
|
echo "Upgrading database to version 3... (backup is $database.bak_v2)"
|
||||||
cp "$database" "$database.bak_v2"
|
cp "$database" "$database.bak_v2"
|
||||||
# Add 'enabled' flag to destinations so individual destinations can be disabled
|
|
||||||
echo 'ALTER TABLE destinations ADD COLUMN enabled INTEGER DEFAULT 1;' >&3
|
echo 'ALTER TABLE destinations ADD COLUMN enabled INTEGER DEFAULT 1;' >&3
|
||||||
# Enable all existing destinations (preserve old behaviour where all were active)
|
|
||||||
Update destinations enabled 1 <<< "1 = 1"
|
Update destinations enabled 1 <<< "1 = 1"
|
||||||
|
|
||||||
Update atom version 3 <<<"1 = 1"
|
Update atom version 3 <<<"1 = 1"
|
||||||
|
|||||||
@ -1,22 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_3_4() {
|
upgradedatabase_3_4() {
|
||||||
echo "Upgrading database to version 4... (backup is $database.bak_v3)"
|
echo "Upgrading database to version 4... (backup is $database.bak_v3)"
|
||||||
cp "$database" "$database.bak_v3"
|
cp "$database" "$database.bak_v3"
|
||||||
# Add releasecountry tag storage (MusicBrainz Album Release Country)
|
|
||||||
echo 'ALTER TABLE tags ADD COLUMN releasecountry TEXT;' >&3
|
echo 'ALTER TABLE tags ADD COLUMN releasecountry TEXT;' >&3
|
||||||
|
|
||||||
Update atom version 4 <<<"1 = 1"
|
Update atom version 4 <<<"1 = 1"
|
||||||
|
|||||||
@ -1,23 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_4_5() {
|
upgradedatabase_4_5() {
|
||||||
echo "Upgrading database to version 5... (backup is $database.bak_v4)"
|
echo "Upgrading database to version 5... (backup is $database.bak_v4)"
|
||||||
cp "$database" "$database.bak_v4"
|
cp "$database" "$database.bak_v4"
|
||||||
# Drop and recreate the trigger so it now also watches releasecountry
|
|
||||||
# (added in v4 but not yet included in the trigger's watched columns)
|
|
||||||
echo 'DROP TRIGGER force_destination_update_on_tag_update;' >&3
|
echo 'DROP TRIGGER force_destination_update_on_tag_update;' >&3
|
||||||
echo '
|
echo '
|
||||||
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
|
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
|
||||||
@ -39,8 +24,6 @@ upgradedatabase_4_5() {
|
|||||||
depth
|
depth
|
||||||
ON tags
|
ON tags
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Reset destination timestamp so the file gets
|
|
||||||
-- re-encoded on next run
|
|
||||||
UPDATE destination_files SET last_change=0
|
UPDATE destination_files SET last_change=0
|
||||||
WHERE source_file_id=old.source_file;
|
WHERE source_file_id=old.source_file;
|
||||||
END;
|
END;
|
||||||
|
|||||||
@ -1,25 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_5_6() {
|
upgradedatabase_5_6() {
|
||||||
echo "Upgrading database to version 6... (backup is $database.bak_v5)"
|
echo "Upgrading database to version 6... (backup is $database.bak_v5)"
|
||||||
cp "$database" "$database.bak_v5"
|
cp "$database" "$database.bak_v5"
|
||||||
echo '
|
echo '
|
||||||
ALTER TABLE tags ADD COLUMN replaygain_alb TEXT;
|
ALTER TABLE tags ADD COLUMN replaygain_alb TEXT;
|
||||||
ALTER TABLE tags ADD COLUMN replaygain_trk TEXT;
|
ALTER TABLE tags ADD COLUMN replaygain_trk TEXT;
|
||||||
-- Recreate trigger to also watch the new ReplayGain columns
|
|
||||||
DROP TRIGGER force_destination_update_on_tag_update;
|
DROP TRIGGER force_destination_update_on_tag_update;
|
||||||
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
|
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
|
||||||
AFTER UPDATE OF
|
AFTER UPDATE OF
|
||||||
|
|||||||
@ -1,25 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_6_7() {
|
upgradedatabase_6_7() {
|
||||||
echo "Upgrading database to version 7... (backup is $database.bak_v6)"
|
echo "Upgrading database to version 7... (backup is $database.bak_v6)"
|
||||||
cp "$database" "$database.bak_v6"
|
cp "$database" "$database.bak_v6"
|
||||||
# In v6 and earlier, destination_files.filename stored absolute paths.
|
|
||||||
# From v7 onwards, filenames are stored relative to the destination
|
|
||||||
# root.
|
|
||||||
# Strip the destination path prefix from all stored filenames.
|
|
||||||
for destination in "${destinations[@]}"
|
for destination in "${destinations[@]}"
|
||||||
do
|
do
|
||||||
echo "UPDATE destination_files SET filename = REPLACE(filename,'${destinationpath[$destination]}/','') WHERE filename LIKE '${destinationpath[$destination]}/%';" >&3
|
echo "UPDATE destination_files SET filename = REPLACE(filename,'${destinationpath[$destination]}/','') WHERE filename LIKE '${destinationpath[$destination]}/%';" >&3
|
||||||
|
|||||||
@ -1,24 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
upgradedatabase_7_8() {
|
upgradedatabase_7_8() {
|
||||||
echo "Upgrading database to version 8... (backup is $database.bak_v7)"
|
echo "Upgrading database to version 8... (backup is $database.bak_v7)"
|
||||||
cp "$database" "$database.bak_v7"
|
cp "$database" "$database.bak_v7"
|
||||||
# This migration only contains a user notice; no schema changes needed.
|
|
||||||
# The bug fixed in v8 was that old destination files were not being
|
|
||||||
# deleted on disk correctly; users must run 'cleandestinations -r' to clean up.
|
|
||||||
echo 'Deletion of old files was failing. Users of previous versions (YOU!) are strongly advised to run cleandestinations with the "-r" flag.'
|
echo 'Deletion of old files was failing. Users of previous versions (YOU!) are strongly advised to run cleandestinations with the "-r" flag.'
|
||||||
read -p "Press Enter to continue..."
|
read -p "Press Enter to continue..."
|
||||||
Update atom version 8 <<<"1 = 1"
|
Update atom version 8 <<<"1 = 1"
|
||||||
|
|||||||
@ -1,34 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
# soxtaskid persists across destinations so encode tasks share a single sox task
|
|
||||||
declare soxtaskid
|
declare soxtaskid
|
||||||
decodeFile() {
|
decodeFile() {
|
||||||
# copy destinations bypass decoding entirely
|
|
||||||
if [[ ${destinationformat["$destination"]} == copy ]]
|
if [[ ${destinationformat["$destination"]} == copy ]]
|
||||||
then
|
then
|
||||||
copied=1
|
copied=1
|
||||||
else
|
else
|
||||||
# Dispatch to the appropriate decoder based on the source
|
|
||||||
# MIME type.
|
|
||||||
# sox_needed is set when normalization, resampling, or channel
|
|
||||||
# up/downmixing is required. Used to determine whether to
|
|
||||||
# insert an intermediate sox processing task.
|
|
||||||
case "$mimetype" in
|
case "$mimetype" in
|
||||||
'video/'*)
|
'video/'*)
|
||||||
# Extract audio if ffmpeg is available
|
|
||||||
(( disablevideo )) && continue
|
(( disablevideo )) && continue
|
||||||
extractAudio
|
extractAudio
|
||||||
if (( ${destinationnormalize["$destination"]}))\
|
if (( ${destinationnormalize["$destination"]}))\
|
||||||
@ -44,8 +22,6 @@ decodeFile() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
'audio/mpeg')
|
'audio/mpeg')
|
||||||
# Copy MP3 as-is if format and quality match
|
|
||||||
# otherwise decode with sox
|
|
||||||
if [[ ${destinationformat[$destination]} = mp3 ]] \
|
if [[ ${destinationformat[$destination]} = mp3 ]] \
|
||||||
&& checkCopy
|
&& checkCopy
|
||||||
then
|
then
|
||||||
@ -54,9 +30,7 @@ decodeFile() {
|
|||||||
decodeSox
|
decodeSox
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
'application/ogg opus'|'audio/ogg opus')
|
'application/ogg opus')
|
||||||
# Copy Opus as-is if format and quality match
|
|
||||||
# otherwise decode with opusdec
|
|
||||||
if [[ ${destinationformat[$destination]} = opus ]] \
|
if [[ ${destinationformat[$destination]} = opus ]] \
|
||||||
&& checkCopy
|
&& checkCopy
|
||||||
then
|
then
|
||||||
@ -77,9 +51,28 @@ decodeFile() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
'application/ogg'*|'audio/ogg'*)
|
'audio/ogg opus')
|
||||||
# Ogg Vorbis: copy if format/quality match
|
if [[ ${destinationformat[$destination]} = opus ]] \
|
||||||
# otherwise decode with sox
|
&& checkCopy
|
||||||
|
then
|
||||||
|
copied=1
|
||||||
|
else
|
||||||
|
(( disableopusdec )) && continue
|
||||||
|
decodeOpusdec
|
||||||
|
if (( ${destinationnormalize["$destination"]}))\
|
||||||
|
|| (
|
||||||
|
[ -n "${destinationfrequency["$destination"]}" ]\
|
||||||
|
&& (( ${rate:-0} != ${destinationfrequency["$destination"]}))\
|
||||||
|
) || (
|
||||||
|
[ -n "${destinationchannels["$destination"]}" ]\
|
||||||
|
&& (( ${channels:-0} != ${destinationchannels["$destination"]} ))
|
||||||
|
)
|
||||||
|
then
|
||||||
|
sox_needed=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
'application/ogg'*)
|
||||||
if [[ ${destinationformat[$destination]} = vorbis ]] \
|
if [[ ${destinationformat[$destination]} = vorbis ]] \
|
||||||
&& checkCopy
|
&& checkCopy
|
||||||
then
|
then
|
||||||
@ -88,14 +81,22 @@ decodeFile() {
|
|||||||
decodeSox
|
decodeSox
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
'audio/x-flac'|'audio/flac')
|
'audio/ogg'*)
|
||||||
# FLAC: always decode via sox
|
if [[ ${destinationformat[$destination]} = vorbis ]] \
|
||||||
# copy for FLAC makes little sense
|
&& checkCopy
|
||||||
|
then
|
||||||
|
copied=1
|
||||||
|
else
|
||||||
|
decodeSox
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
'audio/x-flac')
|
||||||
|
decodeSox
|
||||||
|
;;
|
||||||
|
'audio/flac')
|
||||||
decodeSox
|
decodeSox
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Unknown MIME type: probe with file to detect
|
|
||||||
# Musepack
|
|
||||||
extendedtype=$(file -b "$sourcepath/$filename")
|
extendedtype=$(file -b "$sourcepath/$filename")
|
||||||
case "$extendedtype" in
|
case "$extendedtype" in
|
||||||
*'Musepack '*)
|
*'Musepack '*)
|
||||||
@ -114,9 +115,6 @@ decodeFile() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Truly unknown format: try
|
|
||||||
# ffmpeg if available,
|
|
||||||
# otherwise fall back to sox
|
|
||||||
if (( disablevideo ))
|
if (( disablevideo ))
|
||||||
then
|
then
|
||||||
decodeSox
|
decodeSox
|
||||||
@ -140,10 +138,6 @@ decodeFile() {
|
|||||||
esac
|
esac
|
||||||
if ! (( copied ))
|
if ! (( copied ))
|
||||||
then
|
then
|
||||||
# Insert a decode task if one doesn't already exist for
|
|
||||||
# this source file
|
|
||||||
# keyed by $tmpfile so multiple destinations share a
|
|
||||||
# single decode task
|
|
||||||
if ! decodetaskid=$(
|
if ! decodetaskid=$(
|
||||||
Select tasks id <<<"key = $tmpfile"
|
Select tasks id <<<"key = $tmpfile"
|
||||||
)
|
)
|
||||||
@ -166,18 +160,11 @@ decodeFile() {
|
|||||||
fi
|
fi
|
||||||
if (( sox_needed ))
|
if (( sox_needed ))
|
||||||
then
|
then
|
||||||
# Insert a sox post-processing task chained
|
|
||||||
# after the decode task
|
|
||||||
decodeSox "$tempdir/$tmpfile.wav"
|
decodeSox "$tempdir/$tmpfile.wav"
|
||||||
if ! soxtaskid=$(
|
if ! soxtaskid=$(
|
||||||
Select tasks id <<<"key = $tmpfile"
|
Select tasks id <<<"key = $tmpfile"
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
# Increment the decode task's
|
|
||||||
# required_by counter so cleaner()
|
|
||||||
# waits for all dependent tasks to
|
|
||||||
# finish before cleaning up the
|
|
||||||
# intermediate file
|
|
||||||
parent_required=$(
|
parent_required=$(
|
||||||
Select tasks required_by \
|
Select tasks required_by \
|
||||||
<<<"id = $decodetaskid"
|
<<<"id = $decodetaskid"
|
||||||
|
|||||||
@ -1,26 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
decodeMpcdec() {
|
decodeMpcdec() {
|
||||||
# Set the tmpfile base name for this specific decode task
|
|
||||||
# source file id + decoder name
|
|
||||||
tmpfile="${fileid}mpcdec"
|
tmpfile="${fileid}mpcdec"
|
||||||
# Build mpcdec command: decode Musepack to WAV in tempdir
|
|
||||||
# ${ionice} prepends the ionice invocation string if configured
|
|
||||||
commandline=(${ionice}mpcdec)
|
commandline=(${ionice}mpcdec)
|
||||||
# Encode any literal newlines in the filename as the SQL-safe inline
|
|
||||||
# marker
|
|
||||||
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}" "$tempdir/$tmpfile.wav")
|
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}" "$tempdir/$tmpfile.wav")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
decodeOpusdec() {
|
decodeOpusdec() {
|
||||||
# Set the tmpfile base name for this specific decode task
|
|
||||||
# source file id + decoder name
|
|
||||||
tmpfile="${fileid}opusdec"
|
tmpfile="${fileid}opusdec"
|
||||||
# Build opusdec command: decode Opus to WAV in tempdir
|
|
||||||
# ${ionice} prepends the ionice invocation string if configured
|
|
||||||
commandline=(${ionice}opusdec)
|
commandline=(${ionice}opusdec)
|
||||||
# Encode any literal newlines in the filename as the SQL-safe inline
|
|
||||||
# marker
|
|
||||||
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}" "$tempdir/$tmpfile.wav")
|
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}" "$tempdir/$tmpfile.wav")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,67 +1,36 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
decodeSox() {
|
decodeSox() {
|
||||||
# Build a SoX decode command with optional processing (normalize,
|
|
||||||
# resample, up/downmix)
|
|
||||||
commandline=(${ionice}sox --single-threaded --temp "$tempdir")
|
commandline=(${ionice}sox --single-threaded --temp "$tempdir")
|
||||||
soxoptions_in=''
|
soxoptions_in=''
|
||||||
soxoptions_out=''
|
soxoptions_out=''
|
||||||
|
|
||||||
# Add --norm if output normalization is requested
|
|
||||||
if (( ${destinationnormalize["$destination"]} ))
|
if (( ${destinationnormalize["$destination"]} ))
|
||||||
then
|
then
|
||||||
commandline+=(--norm)
|
commandline+=(--norm)
|
||||||
soxoptions_in+=' --norm'
|
soxoptions_in+=' --norm'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# $1 can be set to pass an already decoded file.
|
|
||||||
# Use the the original file when unset
|
|
||||||
if [ -n "$1" ]
|
if [ -n "$1" ]
|
||||||
then
|
then
|
||||||
commandline+=("$1")
|
commandline+=("$1")
|
||||||
else
|
else
|
||||||
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}")
|
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add resampling if requested
|
|
||||||
if [ -n "${destinationfrequency["$destination"]}" ] \
|
if [ -n "${destinationfrequency["$destination"]}" ] \
|
||||||
&& (( ${rate:-0} != ${destinationfrequency["$destination"]} ))
|
&& (( ${rate:-0} != ${destinationfrequency["$destination"]} ))
|
||||||
then
|
then
|
||||||
commandline+=(-r ${destinationfrequency["$destination"]})
|
commandline+=(-r ${destinationfrequency["$destination"]})
|
||||||
soxoptions_out+=" -r ${destinationfrequency["$destination"]}"
|
soxoptions_out+=" -r ${destinationfrequency["$destination"]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add channel up/downmixing if requested
|
|
||||||
if [ -n "${destinationchannels["$destination"]}" ] \
|
if [ -n "${destinationchannels["$destination"]}" ] \
|
||||||
&& (( ${channels:-0} != ${destinationchannels["$destination"]} ))
|
&& (( ${channels:-0} != ${destinationchannels["$destination"]} ))
|
||||||
then
|
then
|
||||||
commandline+=(-c ${destinationchannels["$destination"]})
|
commandline+=(-c ${destinationchannels["$destination"]})
|
||||||
soxoptions_out+=" -c ${destinationchannels["$destination"]}"
|
soxoptions_out+=" -c ${destinationchannels["$destination"]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Downsample to 16-bit if source resolution exceeds 16 bits
|
|
||||||
if (( ${depth:-0} > 16 ))
|
if (( ${depth:-0} > 16 ))
|
||||||
then
|
then
|
||||||
commandline+=(-b 16)
|
commandline+=(-b 16)
|
||||||
soxoptions_out+=" -b 16"
|
soxoptions_out+=" -b 16"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Encode all sox options into the tmpfile name so different processing
|
|
||||||
# chains get unique WAV files
|
|
||||||
# Avoids conflicts with different samplerate/norm/channel counts
|
|
||||||
tmpfile="$fileid${soxoptions_in// /}${soxoptions_out// /}"
|
tmpfile="$fileid${soxoptions_in// /}${soxoptions_out// /}"
|
||||||
commandline+=("$tempdir/$tmpfile.wav")
|
commandline+=("$tempdir/$tmpfile.wav")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
createDestinations() {
|
createDestinations() {
|
||||||
for destination in ${destinations[@]}
|
for destination in ${destinations[@]}
|
||||||
do
|
do
|
||||||
# Create destination directory if it doesn't exist yet
|
|
||||||
if ! [ -d "${destinationpath["$destination"]}" ]
|
if ! [ -d "${destinationpath["$destination"]}" ]
|
||||||
then
|
then
|
||||||
if ! mkdir -p "${destinationpath["$destination"]}"
|
if ! mkdir -p "${destinationpath["$destination"]}"
|
||||||
@ -25,8 +10,6 @@ createDestinations() {
|
|||||||
exit $EINVDEST
|
exit $EINVDEST
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Ensure the destination has a DB record; store its numeric ID
|
|
||||||
# for later use
|
|
||||||
destinationid["$destination"]=$(
|
destinationid["$destination"]=$(
|
||||||
InsertIfUnset destinations <<<"name $destination ${destinationenabled[\"$destination\"]}"
|
InsertIfUnset destinations <<<"name $destination ${destinationenabled[\"$destination\"]}"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,32 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
updateMimes() {
|
updateMimes() {
|
||||||
# Reset all mime_actions to action=1 (transcode) as the default
|
|
||||||
Update mime_actions action 1 <<<"action != 1"
|
Update mime_actions action 1 <<<"action != 1"
|
||||||
|
|
||||||
# For each destination's skip patterns, set action=0 (exclude from
|
|
||||||
# processing)
|
|
||||||
# Multiple patterns are pipe-separated; split by setting IFS='|'
|
|
||||||
for destination in ${!destinationskipmime[@]}
|
for destination in ${!destinationskipmime[@]}
|
||||||
do
|
do
|
||||||
IFS='|'
|
IFS='|'
|
||||||
for mime_type in ${destinationskipmime["$destination"]}
|
for mime_type in ${destinationskipmime["$destination"]}
|
||||||
do
|
do
|
||||||
IFS="$oldIFS"
|
IFS="$oldIFS"
|
||||||
# Convert config wildcard '*' to SQL wildcard '%'
|
|
||||||
Update mime_type_actions action 0 >/dev/null < <(
|
Update mime_type_actions action 0 >/dev/null < <(
|
||||||
cat <<-EOWhere
|
cat <<-EOWhere
|
||||||
destination_id = ${destinationid["$destination"]}
|
destination_id = ${destinationid["$destination"]}
|
||||||
@ -35,17 +15,12 @@ updateMimes() {
|
|||||||
)
|
)
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
# For each destination's copy-mime patterns, set action=2 (copy
|
|
||||||
# verbatim)
|
|
||||||
# Multiple patterns are pipe-separated; split by setting IFS='|'
|
|
||||||
for destination in ${!destinationcopymime[@]}
|
for destination in ${!destinationcopymime[@]}
|
||||||
do
|
do
|
||||||
IFS='|'
|
IFS='|'
|
||||||
for mime_type in ${destinationcopymime["$destination"]}
|
for mime_type in ${destinationcopymime["$destination"]}
|
||||||
do
|
do
|
||||||
IFS="$oldIFS"
|
IFS="$oldIFS"
|
||||||
# Convert config wildcard '*' to SQL wildcard '%'
|
|
||||||
Update mime_type_actions action 2 >/dev/null < <(
|
Update mime_type_actions action 2 >/dev/null < <(
|
||||||
cat <<-EOWhere
|
cat <<-EOWhere
|
||||||
destination_id = ${destinationid["$destination"]}
|
destination_id = ${destinationid["$destination"]}
|
||||||
|
|||||||
@ -1,30 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
encodeFile::mp3() {
|
encodeFile::mp3() {
|
||||||
# Build lame ABR encode command with all available metadata
|
|
||||||
lameopts=(${ionice}lame --quiet --noreplaygain)
|
lameopts=(${ionice}lame --quiet --noreplaygain)
|
||||||
lameopts+=(-v --abr ${destinationquality[$destination]})
|
lameopts+=(-v --abr ${destinationquality[$destination]})
|
||||||
# Embed ID3 tags for each available metadata field
|
|
||||||
[ -n "$album" ] && lameopts+=(--tl "$album" )
|
[ -n "$album" ] && lameopts+=(--tl "$album" )
|
||||||
[ -n "$artist" ] && lameopts+=(--ta "$artist")
|
[ -n "$artist" ] && lameopts+=(--ta "$artist")
|
||||||
[ -n "$genre" ] && lameopts+=(--tg "$genre")
|
[ -n "$genre" ] && lameopts+=(--tg "$genre")
|
||||||
[ -n "$title" ] && lameopts+=(--tt "$title")
|
[ -n "$title" ] && lameopts+=(--tt "$title")
|
||||||
[ -n "$track" ] && lameopts+=(--tn "$track")
|
[ -n "$track" ] && lameopts+=(--tn "$track")
|
||||||
[ -n "$year" ] && lameopts+=(--ty "$year")
|
[ -n "$year" ] && lameopts+=(--ty "$year")
|
||||||
# Extended tags using ID3v2 frames (TXXX for custom/non-standard fields)
|
|
||||||
[ -n "$albumartist" ] && lameopts+=(--tv TPE2="$albumartist")
|
[ -n "$albumartist" ] && lameopts+=(--tv TPE2="$albumartist")
|
||||||
[ -n "$composer" ] && lameopts+=(--tv TCOM="$composer")
|
[ -n "$composer" ] && lameopts+=(--tv TCOM="$composer")
|
||||||
[ -n "$performer" ] && lameopts+=(--tv TOPE="$performer")
|
[ -n "$performer" ] && lameopts+=(--tv TOPE="$performer")
|
||||||
@ -35,9 +18,6 @@ encodeFile::mp3() {
|
|||||||
[ -n "$replaygain_trk" ] \
|
[ -n "$replaygain_trk" ] \
|
||||||
&& lameopts+=(--tv "TXXX=REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
|
&& lameopts+=(--tv "TXXX=REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
|
||||||
[ -n "$disc" ] && lameopts+=(--tv TPOS="$disc")
|
[ -n "$disc" ] && lameopts+=(--tv TPOS="$disc")
|
||||||
|
|
||||||
# Handle noresample: force lame to use a specific sample rate to prevent
|
|
||||||
# lame's own automatic downsampling at low bitrates
|
|
||||||
if (( ${destinationnoresample[$destination]:-0} == 1 ))
|
if (( ${destinationnoresample[$destination]:-0} == 1 ))
|
||||||
then
|
then
|
||||||
# If 'rate' is not one of these value, it cannot be encoded to
|
# If 'rate' is not one of these value, it cannot be encoded to
|
||||||
@ -45,7 +25,6 @@ encodeFile::mp3() {
|
|||||||
# rate to use.
|
# rate to use.
|
||||||
if [ -n "${destinationfrequency["$destination"]}" ]
|
if [ -n "${destinationfrequency["$destination"]}" ]
|
||||||
then
|
then
|
||||||
# Target frequency was explicitly set; use that
|
|
||||||
case ${destinationfrequency["$destination"]} in
|
case ${destinationfrequency["$destination"]} in
|
||||||
48000) lameopts+=(--resample 48) ;;
|
48000) lameopts+=(--resample 48) ;;
|
||||||
44100) lameopts+=(--resample 44.1) ;;
|
44100) lameopts+=(--resample 44.1) ;;
|
||||||
@ -59,10 +38,8 @@ encodeFile::mp3() {
|
|||||||
esac
|
esac
|
||||||
elif (( rate > 48000 ))
|
elif (( rate > 48000 ))
|
||||||
then
|
then
|
||||||
# Source rate exceeds MP3 maximum; cap at 48kHz
|
|
||||||
lameopts+=(--resample 48)
|
lameopts+=(--resample 48)
|
||||||
else
|
else
|
||||||
# Use the source file's own sample rate
|
|
||||||
case $rate in
|
case $rate in
|
||||||
48000) lameopts+=(--resample 48) ;;
|
48000) lameopts+=(--resample 48) ;;
|
||||||
44100) lameopts+=(--resample 44.1) ;;
|
44100) lameopts+=(--resample 44.1) ;;
|
||||||
@ -76,11 +53,7 @@ encodeFile::mp3() {
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Append input WAV and output MP3 paths to complete the command
|
|
||||||
lameopts+=("$tempdir/$tmpfile.wav" "${destinationpath[$destination]}/$destdir/$destfile.mp3")
|
lameopts+=("$tempdir/$tmpfile.wav" "${destinationpath[$destination]}/$destdir/$destfile.mp3")
|
||||||
|
|
||||||
# Insert the encode task into the DB, linked to the decode (or sox) task
|
|
||||||
# Depend on sox task if it exists, else decode task
|
|
||||||
encodetaskid=$(
|
encodetaskid=$(
|
||||||
Insert tasks <<-EOInsert
|
Insert tasks <<-EOInsert
|
||||||
key ${fileid}lame$destination
|
key ${fileid}lame$destination
|
||||||
@ -90,9 +63,6 @@ encodeFile::mp3() {
|
|||||||
$(
|
$(
|
||||||
for key in ${!lameopts[@]}
|
for key in ${!lameopts[@]}
|
||||||
do
|
do
|
||||||
# Escape special characters that could
|
|
||||||
# interfere with bash glob/brace
|
|
||||||
# expansion
|
|
||||||
cleanedopts="${lameopts[key]//\&/\\\&}"
|
cleanedopts="${lameopts[key]//\&/\\\&}"
|
||||||
cleanedopts="${cleanedopts//\[/\\[}"
|
cleanedopts="${cleanedopts//\[/\\[}"
|
||||||
cleanedopts="${cleanedopts//\]/\\]}"
|
cleanedopts="${cleanedopts//\]/\\]}"
|
||||||
@ -108,8 +78,6 @@ encodeFile::mp3() {
|
|||||||
ascii ${destinationascii["$destination"]}
|
ascii ${destinationascii["$destination"]}
|
||||||
EOInsert
|
EOInsert
|
||||||
)
|
)
|
||||||
# Increment parent task's required_by counter so it won't clean up
|
|
||||||
# until all children finish
|
|
||||||
parent_required=$(
|
parent_required=$(
|
||||||
Select tasks required_by \
|
Select tasks required_by \
|
||||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||||
@ -117,5 +85,5 @@ encodeFile::mp3() {
|
|||||||
Update tasks required_by $((++parent_required)) \
|
Update tasks required_by $((++parent_required)) \
|
||||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||||
progressSpin
|
progressSpin
|
||||||
soxtaskid='' # Clear sox task ID so next destination starts fresh
|
soxtaskid=''
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
encodeFile::opus() {
|
encodeFile::opus() {
|
||||||
# Build opusenc VBR encode command
|
|
||||||
opusencopts=(${ionice}opusenc --quiet)
|
opusencopts=(${ionice}opusenc --quiet)
|
||||||
opusencopts+=(--bitrate ${destinationquality[$destination]})
|
opusencopts+=(--bitrate ${destinationquality[$destination]})
|
||||||
# Add Forward Error Correction if a packet loss percentage was
|
|
||||||
# configured
|
|
||||||
[ -n "${destinationloss["$destination"]}" ] \
|
[ -n "${destinationloss["$destination"]}" ] \
|
||||||
&& opusencopts+=(--expect-loss "${destinationloss["$destination"]}")
|
&& opusencopts+=(--expect-loss "${destinationloss["$destination"]}")
|
||||||
# Embed Ogg comment tags
|
|
||||||
[ -n "$albumartist" ] && opusencopts+=(--comment "ALBUMARTIST=$albumartist")
|
[ -n "$albumartist" ] && opusencopts+=(--comment "ALBUMARTIST=$albumartist")
|
||||||
[ -n "$album" ] && opusencopts+=(--comment "ALBUM=$album")
|
[ -n "$album" ] && opusencopts+=(--comment "ALBUM=$album")
|
||||||
[ -n "$artist" ] && opusencopts+=(--artist "$artist")
|
[ -n "$artist" ] && opusencopts+=(--artist "$artist")
|
||||||
@ -38,8 +20,6 @@ encodeFile::opus() {
|
|||||||
&& opusencopts+=(--comment) \
|
&& opusencopts+=(--comment) \
|
||||||
&& opusencopts+=("REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
|
&& opusencopts+=("REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
|
||||||
[ -n "$title" ] && opusencopts+=(--title "$title")
|
[ -n "$title" ] && opusencopts+=(--title "$title")
|
||||||
# Split "track/total" format: opusenc uses separate TRACKNUMBER and
|
|
||||||
# TRACKTOTAL fields
|
|
||||||
[ -n "$track" ] && opusencopts+=(--comment "TRACKNUMBER=${track%/*}")
|
[ -n "$track" ] && opusencopts+=(--comment "TRACKNUMBER=${track%/*}")
|
||||||
[ -n "${track#*/}" ] && opusencopts+=(--comment "TRACKTOTAL=${track#*/}")
|
[ -n "${track#*/}" ] && opusencopts+=(--comment "TRACKTOTAL=${track#*/}")
|
||||||
[ -n "$year" ] && opusencopts+=(--comment "DATE=$year")
|
[ -n "$year" ] && opusencopts+=(--comment "DATE=$year")
|
||||||
@ -51,8 +31,6 @@ encodeFile::opus() {
|
|||||||
fileid $destfileid
|
fileid $destfileid
|
||||||
filename $destdir/$destfile.opus
|
filename $destdir/$destfile.opus
|
||||||
$(
|
$(
|
||||||
# Escape special characters that could
|
|
||||||
# interfere with bash glob/brace expansion
|
|
||||||
for key in ${!opusencopts[@]}
|
for key in ${!opusencopts[@]}
|
||||||
do
|
do
|
||||||
cleanedopts="${opusencopts[key]//\&/\\\&}"
|
cleanedopts="${opusencopts[key]//\&/\\\&}"
|
||||||
@ -70,8 +48,6 @@ encodeFile::opus() {
|
|||||||
ascii ${destinationascii["$destination"]}
|
ascii ${destinationascii["$destination"]}
|
||||||
EOInsert
|
EOInsert
|
||||||
)
|
)
|
||||||
# Increment parent task's required_by counter so it won't clean up
|
|
||||||
# until all children finish
|
|
||||||
parent_required=$(
|
parent_required=$(
|
||||||
Select tasks required_by \
|
Select tasks required_by \
|
||||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||||
|
|||||||
@ -1,23 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env 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.
|
|
||||||
|
|
||||||
encodeFile::vorbis() {
|
encodeFile::vorbis() {
|
||||||
# Build oggenc quality-based encode command
|
|
||||||
# (-Q = quiet, -q = quality level)
|
|
||||||
oggencopts=(${ionice}oggenc -Q -q ${destinationquality[$destination]})
|
oggencopts=(${ionice}oggenc -Q -q ${destinationquality[$destination]})
|
||||||
# Embed Ogg comment tags using oggenc's -c "FIELD=value" syntax
|
|
||||||
[ -n "$albumartist" ] && oggencopts+=(-c "ALBUMARTIST=$albumartist")
|
[ -n "$albumartist" ] && oggencopts+=(-c "ALBUMARTIST=$albumartist")
|
||||||
[ -n "$album" ] && oggencopts+=(-l "$album")
|
[ -n "$album" ] && oggencopts+=(-l "$album")
|
||||||
[ -n "$artist" ] && oggencopts+=(-a "$artist")
|
[ -n "$artist" ] && oggencopts+=(-a "$artist")
|
||||||
@ -34,7 +17,6 @@ encodeFile::vorbis() {
|
|||||||
[ -n "$title" ] && oggencopts+=(-t "$title")
|
[ -n "$title" ] && oggencopts+=(-t "$title")
|
||||||
[ -n "$track" ] && oggencopts+=(-N "$track")
|
[ -n "$track" ] && oggencopts+=(-N "$track")
|
||||||
[ -n "$year" ] && oggencopts+=(-d "$year")
|
[ -n "$year" ] && oggencopts+=(-d "$year")
|
||||||
# -o output must come before input for oggenc
|
|
||||||
oggencopts+=(-o "${destinationpath[$destination]}/$destdir/$destfile.ogg" "$tempdir/$tmpfile.wav")
|
oggencopts+=(-o "${destinationpath[$destination]}/$destdir/$destfile.ogg" "$tempdir/$tmpfile.wav")
|
||||||
encodetaskid=$(
|
encodetaskid=$(
|
||||||
Insert tasks <<-EOInsert
|
Insert tasks <<-EOInsert
|
||||||
@ -43,8 +25,6 @@ encodeFile::vorbis() {
|
|||||||
fileid $destfileid
|
fileid $destfileid
|
||||||
filename $destdir/$destfile.ogg
|
filename $destdir/$destfile.ogg
|
||||||
$(
|
$(
|
||||||
# Escape special characters that could
|
|
||||||
# interfere with bash glob/brace expansion
|
|
||||||
for key in ${!oggencopts[@]}
|
for key in ${!oggencopts[@]}
|
||||||
do
|
do
|
||||||
cleanedopts="${oggencopts[key]//\&/\\\&}"
|
cleanedopts="${oggencopts[key]//\&/\\\&}"
|
||||||
@ -62,8 +42,6 @@ encodeFile::vorbis() {
|
|||||||
ascii ${destinationascii["$destination"]}
|
ascii ${destinationascii["$destination"]}
|
||||||
EOInsert
|
EOInsert
|
||||||
)
|
)
|
||||||
# Increment parent task's required_by counter so it won't clean up
|
|
||||||
# until all children finish
|
|
||||||
parent_required=$(
|
parent_required=$(
|
||||||
Select tasks required_by \
|
Select tasks required_by \
|
||||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||||
|
|||||||
@ -1,22 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
removeObsoleteFiles() {
|
removeObsoleteFiles() {
|
||||||
# Delete source_files records that were not seen in the latest scan.
|
|
||||||
# The DB's ON DELETE CASCADE will remove tags
|
|
||||||
# ON DELETE SET NULL will let us take care of destination_files later.
|
|
||||||
Delete source_files <<-EOWhere
|
Delete source_files <<-EOWhere
|
||||||
last_seen < $scantime
|
last_seen < $scantime
|
||||||
EOWhere
|
EOWhere
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
sanitizeFile() {
|
sanitizeFile() {
|
||||||
shopt -s extglob
|
shopt -s extglob
|
||||||
string="$1"
|
string="$1"
|
||||||
@ -20,7 +6,7 @@ sanitizeFile() {
|
|||||||
string="${string//\// }"
|
string="${string//\// }"
|
||||||
if (( ${destinationfat32compat[$destination]} ))
|
if (( ${destinationfat32compat[$destination]} ))
|
||||||
then
|
then
|
||||||
# FAT32 forbids these characters in filenames
|
# Filenames can't contain:
|
||||||
string=${string//\?/ }
|
string=${string//\?/ }
|
||||||
string=${string//\\/ }
|
string=${string//\\/ }
|
||||||
string=${string//</ }
|
string=${string//</ }
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
setupDestination() {
|
setupDestination() {
|
||||||
cat <<-EODesc
|
cat <<-EODesc
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
setupDestinations() {
|
setupDestinations() {
|
||||||
cat <<-EODesc
|
cat <<-EODesc
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
setupGeneral() {
|
setupGeneral() {
|
||||||
cat <<-EODesc
|
cat <<-EODesc
|
||||||
|
|
||||||
@ -27,14 +14,11 @@ setupGeneral() {
|
|||||||
1 minute load average between <load> and <load>+1 by adjusting
|
1 minute load average between <load> and <load>+1 by adjusting
|
||||||
concurrency. Initial concurrency will be set to half of that value.
|
concurrency. Initial concurrency will be set to half of that value.
|
||||||
EODesc
|
EODesc
|
||||||
# Check input is an integer
|
|
||||||
# Reused across multiple prompts in this function
|
|
||||||
expr='^[0-9]*$'
|
expr='^[0-9]*$'
|
||||||
comeagain() {
|
comeagain() {
|
||||||
read \
|
read \
|
||||||
-e \
|
-e \
|
||||||
-p'Target load: (integer) ' \
|
-p'Target load: (integer) ' \
|
||||||
# Pre-fill with the existing value when re-running setup
|
|
||||||
${maxload+-i"$maxload"} \
|
${maxload+-i"$maxload"} \
|
||||||
value
|
value
|
||||||
if [ -n "$value" ] && [[ $value =~ $expr ]]
|
if [ -n "$value" ] && [[ $value =~ $expr ]]
|
||||||
@ -42,7 +26,6 @@ setupGeneral() {
|
|||||||
maxload="$value"
|
maxload="$value"
|
||||||
else
|
else
|
||||||
echo "Invalid max-load value: $value" >&2
|
echo "Invalid max-load value: $value" >&2
|
||||||
# Recurse until we get a valid value
|
|
||||||
comeagain
|
comeagain
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -81,7 +64,6 @@ setupGeneral() {
|
|||||||
read \
|
read \
|
||||||
-e \
|
-e \
|
||||||
-p'Ionice: <1-3> [0-7] ' \
|
-p'Ionice: <1-3> [0-7] ' \
|
||||||
# Default to class 3 (idle) when no prior value exists
|
|
||||||
-i"${class:-3} ${niceness}" \
|
-i"${class:-3} ${niceness}" \
|
||||||
class niceness
|
class niceness
|
||||||
case $class in
|
case $class in
|
||||||
@ -105,7 +87,6 @@ setupGeneral() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
# Best-effort class; niceness 0-7 is mandatory
|
|
||||||
if [ -n "$niceness" ] \
|
if [ -n "$niceness" ] \
|
||||||
&& (( niceness >= 0 && niceness <= 7 ))
|
&& (( niceness >= 0 && niceness <= 7 ))
|
||||||
then
|
then
|
||||||
@ -117,7 +98,6 @@ setupGeneral() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
# Idle class; no niceness level is accepted
|
|
||||||
ionice="ionice -c3 "
|
ionice="ionice -c3 "
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@ -135,7 +115,6 @@ setupGeneral() {
|
|||||||
sqlite) and temporary WAVE files will be created. Note that debug logs
|
sqlite) and temporary WAVE files will be created. Note that debug logs
|
||||||
(if enabled) will go there too.
|
(if enabled) will go there too.
|
||||||
EODesc
|
EODesc
|
||||||
# Tab-completion (-e) works here because readline is active
|
|
||||||
read \
|
read \
|
||||||
-e \
|
-e \
|
||||||
-i"${tempdir:-$HOME/.atom/tmp}" \
|
-i"${tempdir:-$HOME/.atom/tmp}" \
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
setupRegen() {
|
setupRegen() {
|
||||||
(( regen )) && return 0
|
(( regen )) && return 0
|
||||||
echo "Parameter $1 for destination $destination changed."
|
echo "Parameter $1 for destination $destination changed."
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
cat <<-EOStartConf
|
cat <<-EOStartConf
|
||||||
You will now be asked (hopefully) simple questions to help you configure AtOM's
|
You will now be asked (hopefully) simple questions to help you configure AtOM's
|
||||||
@ -20,17 +7,11 @@ behavior.
|
|||||||
|
|
||||||
Completion is available for prompts asking for a paths or filenames.
|
Completion is available for prompts asking for a paths or filenames.
|
||||||
EOStartConf
|
EOStartConf
|
||||||
# Collect all configuration sections in order
|
|
||||||
setupGeneral
|
setupGeneral
|
||||||
setupSource
|
setupSource
|
||||||
setupDestinations
|
setupDestinations
|
||||||
# Clear the regex used by validation loops inside setup sub-functions
|
|
||||||
unset expr
|
unset expr
|
||||||
# Write the newly gathered config to a temp file so the original is
|
|
||||||
# preserved until the user confirms
|
|
||||||
writeConfig >"$cffile".tmp
|
writeConfig >"$cffile".tmp
|
||||||
# Unset all config variables so getConfig can repopulate them cleanly
|
|
||||||
# from the temp file; avoids stale values leaking into the review
|
|
||||||
unset \
|
unset \
|
||||||
sourcepath \
|
sourcepath \
|
||||||
skippeddirectories \
|
skippeddirectories \
|
||||||
@ -56,8 +37,6 @@ Completion is available for prompts asking for a paths or filenames.
|
|||||||
destinationnoresample \
|
destinationnoresample \
|
||||||
destinationrenamepath \
|
destinationrenamepath \
|
||||||
destinationskipmime
|
destinationskipmime
|
||||||
# Re-declare per-destination variables as associative arrays so
|
|
||||||
# getConfig can populate them with [destinationname]=value entries
|
|
||||||
declare -A \
|
declare -A \
|
||||||
destinationchannels \
|
destinationchannels \
|
||||||
destinationfat32compat \
|
destinationfat32compat \
|
||||||
@ -75,8 +54,6 @@ Completion is available for prompts asking for a paths or filenames.
|
|||||||
destinationnoresample \
|
destinationnoresample \
|
||||||
destinationrenamepath \
|
destinationrenamepath \
|
||||||
destinationskipmime
|
destinationskipmime
|
||||||
# Point getConfig at the temp file so the review reflects exactly what
|
|
||||||
# would be written, not the old on-disk config
|
|
||||||
oldcffile="$cffile"
|
oldcffile="$cffile"
|
||||||
cffile="$cffile".tmp
|
cffile="$cffile".tmp
|
||||||
getConfig
|
getConfig
|
||||||
@ -84,7 +61,6 @@ Completion is available for prompts asking for a paths or filenames.
|
|||||||
echo $'Please review your new configuration:\n'
|
echo $'Please review your new configuration:\n'
|
||||||
printConfig
|
printConfig
|
||||||
}| less -F -e
|
}| less -F -e
|
||||||
# Restore the original config path before deciding whether to commit
|
|
||||||
cffile="$oldcffile"
|
cffile="$oldcffile"
|
||||||
read -p'Write config file? [y/N] ' do_write
|
read -p'Write config file? [y/N] ' do_write
|
||||||
case $do_write in
|
case $do_write in
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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.
|
|
||||||
|
|
||||||
setupSource() {
|
setupSource() {
|
||||||
cat <<-EODesc
|
cat <<-EODesc
|
||||||
|
|
||||||
@ -31,7 +18,6 @@ setupSource() {
|
|||||||
-i"${sourcepath:-/var/lib/mpd/music}" \
|
-i"${sourcepath:-/var/lib/mpd/music}" \
|
||||||
-p'Music collection (<TAB> for completion): ' \
|
-p'Music collection (<TAB> for completion): ' \
|
||||||
sourcepath
|
sourcepath
|
||||||
# Reject paths that don't exist; AtOM doesn't create the source
|
|
||||||
if ! [ -d "$sourcepath" ]
|
if ! [ -d "$sourcepath" ]
|
||||||
then
|
then
|
||||||
echo "$sourcepath does not exist or is not a" \
|
echo "$sourcepath does not exist or is not a" \
|
||||||
@ -48,17 +34,12 @@ setupSource() {
|
|||||||
|
|
||||||
This prompt will loop until an empty string is encountered.
|
This prompt will loop until an empty string is encountered.
|
||||||
EODesc
|
EODesc
|
||||||
# Change into the source directory so readline tab-completion resolves
|
|
||||||
# relative paths correctly while the user types skip entries
|
|
||||||
cd "$sourcepath"
|
cd "$sourcepath"
|
||||||
# Remember how many skip entries already exist so we know when to stop
|
|
||||||
# re-presenting existing entries vs. treating blanks as "done"
|
|
||||||
count=${#skippeddirectories[@]}
|
count=${#skippeddirectories[@]}
|
||||||
for (( i=0 ; 1 ; i++ ))
|
for (( i=0 ; 1 ; i++ ))
|
||||||
do
|
do
|
||||||
read \
|
read \
|
||||||
-e \
|
-e \
|
||||||
# Pre-fill with the existing entry at this index, if any
|
|
||||||
${skippeddirectories[i]+-i"${skippeddirectories[i]}"}\
|
${skippeddirectories[i]+-i"${skippeddirectories[i]}"}\
|
||||||
-p'Skip: ' \
|
-p'Skip: ' \
|
||||||
value
|
value
|
||||||
@ -67,14 +48,11 @@ setupSource() {
|
|||||||
skippeddirectories[i]="$value"
|
skippeddirectories[i]="$value"
|
||||||
elif (( i < count ))
|
elif (( i < count ))
|
||||||
then
|
then
|
||||||
# Blank input on a previously set entry removes it
|
|
||||||
unset skippeddirectories[i]
|
unset skippeddirectories[i]
|
||||||
else
|
else
|
||||||
# Blank input beyond the old list signals end of input
|
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
unset count
|
unset count
|
||||||
# Return to the prior directory; output suppressed to keep the UI clean
|
|
||||||
cd - >/dev/null
|
cd - >/dev/null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS source_files (
|
|||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
filename TEXT UNIQUE NOT NULL,
|
filename TEXT UNIQUE NOT NULL,
|
||||||
size INTEGER NOT NULL,
|
size INTEGER NOT NULL,
|
||||||
|
hash TEXT,
|
||||||
mime_type INTEGER,
|
mime_type INTEGER,
|
||||||
last_change FLOAT NOT NULL DEFAULT (strftime('%s','now')),
|
last_change FLOAT NOT NULL DEFAULT (strftime('%s','now')),
|
||||||
last_seen INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
last_seen INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user