Compare commits
6 Commits
ee119f07a4
...
0f13154d6f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f13154d6f | ||
|
|
3995e92323 | ||
|
|
c9a21637c6 | ||
|
|
d0175fa03d | ||
|
|
6474bcab25 | ||
|
|
22549072c3 |
@ -1,4 +1,18 @@
|
||||
#!/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 table < where_key where_operator where_value
|
||||
# [where_key where_operator where_value
|
||||
@ -10,13 +24,16 @@ Delete() {
|
||||
value \
|
||||
where_statement \
|
||||
results
|
||||
# Build WHERE clause from stdin: one "key op value" triple per line
|
||||
while read key operator value
|
||||
do
|
||||
(( ${#where_statement} )) && where_statement+=( "AND" )
|
||||
if [[ $value == NULL ]]
|
||||
then
|
||||
# NULL comparisons require IS NULL, not = "NULL"
|
||||
where_statement+=( "$key is NULL" )
|
||||
else
|
||||
# Double embedded quotes to safely escape string values
|
||||
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
||||
fi
|
||||
done
|
||||
|
||||
@ -1,14 +1,32 @@
|
||||
#!/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 table [no_id] < 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 \
|
||||
table="$1" \
|
||||
no_id="${2:-0}" \
|
||||
insert_keys \
|
||||
insert_values \
|
||||
results
|
||||
# Build column list and value list from stdin key-value pairs
|
||||
while read key value
|
||||
do
|
||||
(( ${#insert_keys} )) && insert_keys+=","
|
||||
@ -16,16 +34,24 @@ Insert() {
|
||||
(( ${#insert_values} )) && insert_values+=","
|
||||
case $value in
|
||||
'::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::/}"
|
||||
insert_values+='"'"${value//\"/\"\"}"'"'
|
||||
;;
|
||||
'NULL')
|
||||
# Insert SQL NULL (not the string "NULL")
|
||||
insert_values+="NULL"
|
||||
;;
|
||||
+([0-9])?(.+([0-9])))
|
||||
# Pure integer or decimal: insert unquoted for
|
||||
# numeric storage
|
||||
insert_values+=$value
|
||||
;;
|
||||
*)
|
||||
# General string: restore encoded newlines,
|
||||
# then quote
|
||||
value=${value//::AtOM:NewLine:SQL:Inline::/$'\n'}
|
||||
insert_values+='"'"${value//\"/\"\"}"'"'
|
||||
;;
|
||||
@ -35,6 +61,7 @@ Insert() {
|
||||
"( $insert_keys )" \
|
||||
"VALUES" \
|
||||
"( $insert_values );" >&3
|
||||
# Unless no_id is set, return the auto-assigned row ID
|
||||
(( no_id )) || {
|
||||
echo 'SELECT LAST_INSERT_ROWID();' >&3
|
||||
read -u4 -r -d $'\0' results
|
||||
|
||||
@ -1,6 +1,23 @@
|
||||
#!/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 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 \
|
||||
table="$1" \
|
||||
no_id="${2:-0}" \
|
||||
@ -10,27 +27,32 @@ InsertIfUnset() {
|
||||
results \
|
||||
value \
|
||||
values
|
||||
# Read all key-value pairs from stdin into parallel arrays
|
||||
while read key value
|
||||
do
|
||||
keys+=( "$key" )
|
||||
values+=( "$value" )
|
||||
done
|
||||
# Choose which column to return: first key column if no_id, else 'id'
|
||||
if (( no_id ))
|
||||
then
|
||||
column="${keys[0]}"
|
||||
else
|
||||
column='id'
|
||||
fi
|
||||
# Check if a matching row already exists
|
||||
if ! results=$(
|
||||
Select "$table" "$column" < <(
|
||||
for key in ${!keys[@]}
|
||||
do
|
||||
# Strip ::AtOM:FT:: for WHERE comparison
|
||||
echo "${keys[$key]}" = \
|
||||
"${values[$key]//::AtOM:FT::}"
|
||||
done
|
||||
)
|
||||
)
|
||||
then
|
||||
# Row not found: insert it and return the new id
|
||||
results=$(
|
||||
Insert "$table" < <(
|
||||
for key in ${!keys[@]}
|
||||
|
||||
@ -1,4 +1,18 @@
|
||||
#!/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 table set_key set_value [set_key set_value […]] < where_key where_value
|
||||
# [where_key where_value
|
||||
@ -15,6 +29,8 @@ InsertOrUpdate() {
|
||||
what \
|
||||
results
|
||||
shift
|
||||
# Parse positional args as alternating key/value pairs for the SET
|
||||
# clause
|
||||
what=key
|
||||
for argument
|
||||
do
|
||||
@ -29,11 +45,13 @@ InsertOrUpdate() {
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Read WHERE conditions from stdin
|
||||
while read key value
|
||||
do
|
||||
keys+=( "$key" )
|
||||
values+=( "$value" )
|
||||
done
|
||||
# Check if a matching row exists using the WHERE keys
|
||||
if results=$(
|
||||
Select "$table" ${keys[0]} < <(
|
||||
for key in ${!keys[@]}
|
||||
@ -43,6 +61,7 @@ InsertOrUpdate() {
|
||||
)
|
||||
)
|
||||
then
|
||||
# Row exists: update it with the SET values
|
||||
Update "$table" "$@" < <(
|
||||
for key in ${!keys[@]}
|
||||
do
|
||||
@ -50,6 +69,8 @@ InsertOrUpdate() {
|
||||
done
|
||||
)
|
||||
else
|
||||
# Row not found: insert combining SET columns and WHERE-match
|
||||
# columns
|
||||
results=$(
|
||||
Insert "$table" < <(
|
||||
for key in ${!set_keys[@]}
|
||||
|
||||
@ -1,4 +1,18 @@
|
||||
#!/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 table [col1 [col2 [..]]] < WHERE_key WHERE_operator WHERE_value
|
||||
# [WHERE_key WHERE_operator WHERE_value
|
||||
@ -11,23 +25,28 @@ Select() {
|
||||
results \
|
||||
where_statement
|
||||
shift
|
||||
# Build column list
|
||||
for col
|
||||
do
|
||||
(( ${#columns} )) && columns+=','
|
||||
columns+="$col"
|
||||
done
|
||||
# Build WHERE clause from stdin triplets
|
||||
while read key operator value
|
||||
do
|
||||
(( ${#where_statement} )) && where_statement+=( "AND" )
|
||||
# Restore encoded newlines before embedding in SQL
|
||||
value=${value//::AtOM:NewLine:SQL:Inline::/$'\n'}
|
||||
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
||||
done
|
||||
# Use IFNULL so SQLite always produces output.
|
||||
echo "SELECT IFNULL(" \
|
||||
"(SELECT $columns FROM $table" \
|
||||
"WHERE ${where_statement[@]})" \
|
||||
",'SQL::Select:not found'" \
|
||||
");" >&3
|
||||
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" ]]
|
||||
then
|
||||
echo "$results"
|
||||
|
||||
@ -1,4 +1,18 @@
|
||||
#!/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 table set_key set_value [set_key set_value […]] < where_key where_operator where_value
|
||||
# [where_key where_operator where_value
|
||||
@ -16,17 +30,22 @@ Update() {
|
||||
where_statement \
|
||||
results
|
||||
shift
|
||||
# Parse positional args as alternating key/value for the SET clause
|
||||
what=key
|
||||
for argument
|
||||
do
|
||||
case $what in
|
||||
key)
|
||||
# Backtick-quote column name to handle reserved
|
||||
# words
|
||||
set_statement="${set_statement:+${set_statement},}\`$argument\`"
|
||||
what=value
|
||||
;;
|
||||
value)
|
||||
case $argument in
|
||||
'::AtOM:FT::'*)
|
||||
# Force-text: strip prefix and
|
||||
# quote as string
|
||||
argument="${argument//::AtOM:FT::/}"
|
||||
set_statement+=" = "'"'"${argument//\"/\"\"}"'"'
|
||||
;;
|
||||
@ -34,6 +53,7 @@ Update() {
|
||||
set_statement+=" = NULL"
|
||||
;;
|
||||
+([0-9])?(.+([0-9])))
|
||||
# Numeric value: store unquoted
|
||||
set_statement+=" = $argument"
|
||||
;;
|
||||
*)
|
||||
@ -44,6 +64,7 @@ Update() {
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Build WHERE clause from stdin
|
||||
while read key operator value
|
||||
do
|
||||
(( ${#where_statement} )) && where_statement+=( "AND" )
|
||||
@ -52,6 +73,7 @@ Update() {
|
||||
where_statement+=( "$key is NULL" )
|
||||
;;
|
||||
+([0-9.]))
|
||||
# Numeric: compare without quotes
|
||||
where_statement+=( "$key $operator $value" )
|
||||
;;
|
||||
*)
|
||||
|
||||
@ -1,26 +1,53 @@
|
||||
#!/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
|
||||
checkDatabaseVersion() {
|
||||
local dbversion
|
||||
# Try to read the stored version from the 'atom' metadata table
|
||||
if dbversion=$(Select atom version <<<"\"1\" = 1")
|
||||
then
|
||||
if (( dbversion == currentdbversion ))
|
||||
then
|
||||
return 0
|
||||
return 0 # Already up to date
|
||||
elif (( dbversion < currentdbversion ))
|
||||
then
|
||||
# Run sequential upgrade functions until we reach
|
||||
# `$currentdbversion`
|
||||
until (( dbversion == currentdbversion ))
|
||||
do
|
||||
# Dynamically calls e.g. upgradedatabase_3_4
|
||||
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")
|
||||
done
|
||||
else
|
||||
# DB was created by a newer AtOM; we can't run and
|
||||
# ensure consistency
|
||||
echo "Database schema version $dbversion is" \
|
||||
"higher thanthat of this version of" \
|
||||
"AtOM ($currentdbversion). Bailing out." >&2
|
||||
exit $EDBVERSION
|
||||
fi
|
||||
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"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -1,11 +1,31 @@
|
||||
#!/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() {
|
||||
# Run VACUUM to reclaim space and defragment the database before closing
|
||||
echo 'vacuum;' >&3
|
||||
# Tell sqlite3 to exit cleanly
|
||||
echo .quit >&3
|
||||
(( debug )) && echo -n "Waiting for SQLite to terminate... "
|
||||
# Close the debug tee fd if it was opened (debug level > 2)
|
||||
(( debug > 2 )) && exec 5>&-
|
||||
# Wait for the sqlite3 background process to fully exit
|
||||
wait $db_pid
|
||||
(( debug )) && echo OK
|
||||
# Close the write end of the SQLite input FIFO (FD 3)
|
||||
exec 3>&-
|
||||
# Close the read end of the SQLite output FIFO (FD 4)
|
||||
exec 4<&-
|
||||
}
|
||||
|
||||
@ -1,11 +1,35 @@
|
||||
#!/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() {
|
||||
local \
|
||||
populate_db
|
||||
|
||||
# If the DB file doesn't exist yet, mark it for schema population
|
||||
[[ -f "$database" ]] || populate_db=1
|
||||
|
||||
# Create named FIFOs for bidirectional communication with sqlite3
|
||||
rm -f "$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 \
|
||||
-newline $'::AtOM:SQL:EOL::\n' \
|
||||
"$database" \
|
||||
@ -13,21 +37,42 @@ openDatabase() {
|
||||
| stdbuf -o0 \
|
||||
sed 's/::AtOM:SQL:EOL::/\x0/g;s/\(\x0\)\xA/\1/g' \
|
||||
> "$tempdir/sqlite.out" &
|
||||
# Store the PID of the background sqlite3 process so we can wait for it
|
||||
# to exit
|
||||
db_pid=$!
|
||||
|
||||
# Open FD 3 as the write end (send SQL commands to sqlite3)
|
||||
exec 3> "$tempdir"/sqlite.in
|
||||
# Open FD 4 as the read end (receive query results from sqlite3)
|
||||
exec 4< "$tempdir"/sqlite.out
|
||||
|
||||
# FIFOs can be deleted immediately after opening; the fds keep them
|
||||
# alive
|
||||
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 ))
|
||||
then
|
||||
exec 5>&3
|
||||
exec 3> >(tee -a "$tempdir/debug.log" >&5)
|
||||
fi
|
||||
|
||||
# If new database, populate schema from the SQL schema file
|
||||
(( populate_db )) && cat $schema >&3
|
||||
|
||||
# Configure sqlite3 output separator to match what we parse with ::AtOM:SQL:Sep::
|
||||
echo '.separator ::AtOM:SQL:Sep::' >&3
|
||||
# Enforce referential integrity
|
||||
echo 'PRAGMA foreign_keys = ON;' >&3
|
||||
# Allow trigger chains
|
||||
echo 'PRAGMA recursive_triggers = ON;' >&3
|
||||
# Keep temp tables in memory
|
||||
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
|
||||
|
||||
# Drain the initial empty result sqlite3 sends on startup
|
||||
read -u4 -r -d $'\0'
|
||||
unset REPLY
|
||||
checkDatabaseVersion
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
local data \
|
||||
datas \
|
||||
@ -9,8 +22,11 @@ upgradedatabase_1_2() {
|
||||
fat32
|
||||
echo "Upgrading database to version 2... (backup is $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 ascii INTEGER;' >&3
|
||||
# Read all existing destination_files rows to migrate rename_pattern format
|
||||
# Old format embedded fat32compat after a colon: "pattern:fat32value"
|
||||
echo '
|
||||
SELECT id,
|
||||
rename_pattern
|
||||
@ -24,14 +40,17 @@ SELECT "AtOM::NoMoreData";' >&3
|
||||
datas+=( "$data" )
|
||||
read -u4 -r -d $'\0' data
|
||||
done
|
||||
# Ensure consistency by performing all updates in a single transaction
|
||||
echo 'BEGIN TRANSACTION;' >&3
|
||||
for data in "${datas[@]}"
|
||||
do
|
||||
id="${data%%::AtOM:SQL:Sep::*}"
|
||||
rename_pattern="${data#*::AtOM:SQL:Sep::}"
|
||||
# Split "pattern:fat32" on the colon separator
|
||||
IFS=':'
|
||||
read pattern fat32 <<<"$rename_pattern"
|
||||
IFS="$oldIFS"
|
||||
# ASCII-only didn't exist in v1; default to off
|
||||
Update destination_files \
|
||||
rename_pattern "$pattern" \
|
||||
fat32compat "$fat32" \
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
local data \
|
||||
datas \
|
||||
@ -7,7 +20,9 @@ upgradedatabase_2_3() {
|
||||
destination
|
||||
echo "Upgrading database to version 3... (backup is $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
|
||||
# Enable all existing destinations (preserve old behaviour where all were active)
|
||||
Update destinations enabled 1 <<< "1 = 1"
|
||||
|
||||
Update atom version 3 <<<"1 = 1"
|
||||
|
||||
@ -1,8 +1,22 @@
|
||||
#!/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() {
|
||||
echo "Upgrading database to version 4... (backup is $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
|
||||
|
||||
Update atom version 4 <<<"1 = 1"
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
#!/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() {
|
||||
echo "Upgrading database to version 5... (backup is $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 '
|
||||
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
|
||||
@ -24,6 +39,8 @@ upgradedatabase_4_5() {
|
||||
depth
|
||||
ON tags
|
||||
BEGIN
|
||||
-- Reset destination timestamp so the file gets
|
||||
-- re-encoded on next run
|
||||
UPDATE destination_files SET last_change=0
|
||||
WHERE source_file_id=old.source_file;
|
||||
END;
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
#!/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() {
|
||||
echo "Upgrading database to version 6... (backup is $database.bak_v5)"
|
||||
cp "$database" "$database.bak_v5"
|
||||
echo '
|
||||
ALTER TABLE tags ADD COLUMN replaygain_alb 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;
|
||||
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
|
||||
AFTER UPDATE OF
|
||||
|
||||
@ -1,8 +1,25 @@
|
||||
#!/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() {
|
||||
echo "Upgrading database to version 7... (backup is $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[@]}"
|
||||
do
|
||||
echo "UPDATE destination_files SET filename = REPLACE(filename,'${destinationpath[$destination]}/','') WHERE filename LIKE '${destinationpath[$destination]}/%';" >&3
|
||||
|
||||
@ -1,8 +1,24 @@
|
||||
#!/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() {
|
||||
echo "Upgrading database to version 8... (backup is $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.'
|
||||
read -p "Press Enter to continue..."
|
||||
Update atom version 8 <<<"1 = 1"
|
||||
|
||||
@ -1,12 +1,34 @@
|
||||
#!/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
|
||||
decodeFile() {
|
||||
# copy destinations bypass decoding entirely
|
||||
if [[ ${destinationformat["$destination"]} == copy ]]
|
||||
then
|
||||
copied=1
|
||||
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
|
||||
'video/'*)
|
||||
# Extract audio if ffmpeg is available
|
||||
(( disablevideo )) && continue
|
||||
extractAudio
|
||||
if (( ${destinationnormalize["$destination"]}))\
|
||||
@ -22,6 +44,8 @@ decodeFile() {
|
||||
fi
|
||||
;;
|
||||
'audio/mpeg')
|
||||
# Copy MP3 as-is if format and quality match
|
||||
# otherwise decode with sox
|
||||
if [[ ${destinationformat[$destination]} = mp3 ]] \
|
||||
&& checkCopy
|
||||
then
|
||||
@ -30,7 +54,9 @@ decodeFile() {
|
||||
decodeSox
|
||||
fi
|
||||
;;
|
||||
'application/ogg opus')
|
||||
'application/ogg opus'|'audio/ogg opus')
|
||||
# Copy Opus as-is if format and quality match
|
||||
# otherwise decode with opusdec
|
||||
if [[ ${destinationformat[$destination]} = opus ]] \
|
||||
&& checkCopy
|
||||
then
|
||||
@ -51,28 +77,9 @@ decodeFile() {
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
'audio/ogg opus')
|
||||
if [[ ${destinationformat[$destination]} = opus ]] \
|
||||
&& 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'*)
|
||||
'application/ogg'*|'audio/ogg'*)
|
||||
# Ogg Vorbis: copy if format/quality match
|
||||
# otherwise decode with sox
|
||||
if [[ ${destinationformat[$destination]} = vorbis ]] \
|
||||
&& checkCopy
|
||||
then
|
||||
@ -81,22 +88,14 @@ decodeFile() {
|
||||
decodeSox
|
||||
fi
|
||||
;;
|
||||
'audio/ogg'*)
|
||||
if [[ ${destinationformat[$destination]} = vorbis ]] \
|
||||
&& checkCopy
|
||||
then
|
||||
copied=1
|
||||
else
|
||||
decodeSox
|
||||
fi
|
||||
;;
|
||||
'audio/x-flac')
|
||||
decodeSox
|
||||
;;
|
||||
'audio/flac')
|
||||
'audio/x-flac'|'audio/flac')
|
||||
# FLAC: always decode via sox
|
||||
# copy for FLAC makes little sense
|
||||
decodeSox
|
||||
;;
|
||||
*)
|
||||
# Unknown MIME type: probe with file to detect
|
||||
# Musepack
|
||||
extendedtype=$(file -b "$sourcepath/$filename")
|
||||
case "$extendedtype" in
|
||||
*'Musepack '*)
|
||||
@ -115,6 +114,9 @@ decodeFile() {
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Truly unknown format: try
|
||||
# ffmpeg if available,
|
||||
# otherwise fall back to sox
|
||||
if (( disablevideo ))
|
||||
then
|
||||
decodeSox
|
||||
@ -138,6 +140,10 @@ decodeFile() {
|
||||
esac
|
||||
if ! (( copied ))
|
||||
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=$(
|
||||
Select tasks id <<<"key = $tmpfile"
|
||||
)
|
||||
@ -160,11 +166,18 @@ decodeFile() {
|
||||
fi
|
||||
if (( sox_needed ))
|
||||
then
|
||||
# Insert a sox post-processing task chained
|
||||
# after the decode task
|
||||
decodeSox "$tempdir/$tmpfile.wav"
|
||||
if ! soxtaskid=$(
|
||||
Select tasks id <<<"key = $tmpfile"
|
||||
)
|
||||
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=$(
|
||||
Select tasks required_by \
|
||||
<<<"id = $decodetaskid"
|
||||
|
||||
@ -1,6 +1,26 @@
|
||||
#!/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() {
|
||||
# Set the tmpfile base name for this specific decode task
|
||||
# source file id + decoder name
|
||||
tmpfile="${fileid}mpcdec"
|
||||
# Build mpcdec command: decode Musepack to WAV in tempdir
|
||||
# ${ionice} prepends the ionice invocation string if configured
|
||||
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")
|
||||
}
|
||||
|
||||
@ -1,6 +1,26 @@
|
||||
#!/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() {
|
||||
# Set the tmpfile base name for this specific decode task
|
||||
# source file id + decoder name
|
||||
tmpfile="${fileid}opusdec"
|
||||
# Build opusdec command: decode Opus to WAV in tempdir
|
||||
# ${ionice} prepends the ionice invocation string if configured
|
||||
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")
|
||||
}
|
||||
|
||||
@ -1,36 +1,67 @@
|
||||
#!/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() {
|
||||
# Build a SoX decode command with optional processing (normalize,
|
||||
# resample, up/downmix)
|
||||
commandline=(${ionice}sox --single-threaded --temp "$tempdir")
|
||||
soxoptions_in=''
|
||||
soxoptions_out=''
|
||||
|
||||
# Add --norm if output normalization is requested
|
||||
if (( ${destinationnormalize["$destination"]} ))
|
||||
then
|
||||
commandline+=(--norm)
|
||||
soxoptions_in+=' --norm'
|
||||
fi
|
||||
|
||||
# $1 can be set to pass an already decoded file.
|
||||
# Use the the original file when unset
|
||||
if [ -n "$1" ]
|
||||
then
|
||||
commandline+=("$1")
|
||||
else
|
||||
commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}")
|
||||
fi
|
||||
|
||||
# Add resampling if requested
|
||||
if [ -n "${destinationfrequency["$destination"]}" ] \
|
||||
&& (( ${rate:-0} != ${destinationfrequency["$destination"]} ))
|
||||
then
|
||||
commandline+=(-r ${destinationfrequency["$destination"]})
|
||||
soxoptions_out+=" -r ${destinationfrequency["$destination"]}"
|
||||
fi
|
||||
|
||||
# Add channel up/downmixing if requested
|
||||
if [ -n "${destinationchannels["$destination"]}" ] \
|
||||
&& (( ${channels:-0} != ${destinationchannels["$destination"]} ))
|
||||
then
|
||||
commandline+=(-c ${destinationchannels["$destination"]})
|
||||
soxoptions_out+=" -c ${destinationchannels["$destination"]}"
|
||||
fi
|
||||
|
||||
# Downsample to 16-bit if source resolution exceeds 16 bits
|
||||
if (( ${depth:-0} > 16 ))
|
||||
then
|
||||
commandline+=(-b 16)
|
||||
soxoptions_out+=" -b 16"
|
||||
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// /}"
|
||||
commandline+=("$tempdir/$tmpfile.wav")
|
||||
}
|
||||
|
||||
@ -1,7 +1,22 @@
|
||||
#!/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() {
|
||||
for destination in ${destinations[@]}
|
||||
do
|
||||
# Create destination directory if it doesn't exist yet
|
||||
if ! [ -d "${destinationpath["$destination"]}" ]
|
||||
then
|
||||
if ! mkdir -p "${destinationpath["$destination"]}"
|
||||
@ -10,6 +25,8 @@ createDestinations() {
|
||||
exit $EINVDEST
|
||||
fi
|
||||
fi
|
||||
# Ensure the destination has a DB record; store its numeric ID
|
||||
# for later use
|
||||
destinationid["$destination"]=$(
|
||||
InsertIfUnset destinations <<<"name $destination ${destinationenabled[\"$destination\"]}"
|
||||
)
|
||||
|
||||
@ -1,12 +1,32 @@
|
||||
#!/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() {
|
||||
# Reset all mime_actions to action=1 (transcode) as the default
|
||||
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[@]}
|
||||
do
|
||||
IFS='|'
|
||||
for mime_type in ${destinationskipmime["$destination"]}
|
||||
do
|
||||
IFS="$oldIFS"
|
||||
# Convert config wildcard '*' to SQL wildcard '%'
|
||||
Update mime_type_actions action 0 >/dev/null < <(
|
||||
cat <<-EOWhere
|
||||
destination_id = ${destinationid["$destination"]}
|
||||
@ -15,12 +35,17 @@ updateMimes() {
|
||||
)
|
||||
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[@]}
|
||||
do
|
||||
IFS='|'
|
||||
for mime_type in ${destinationcopymime["$destination"]}
|
||||
do
|
||||
IFS="$oldIFS"
|
||||
# Convert config wildcard '*' to SQL wildcard '%'
|
||||
Update mime_type_actions action 2 >/dev/null < <(
|
||||
cat <<-EOWhere
|
||||
destination_id = ${destinationid["$destination"]}
|
||||
|
||||
@ -1,13 +1,30 @@
|
||||
#!/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() {
|
||||
# Build lame ABR encode command with all available metadata
|
||||
lameopts=(${ionice}lame --quiet --noreplaygain)
|
||||
lameopts+=(-v --abr ${destinationquality[$destination]})
|
||||
# Embed ID3 tags for each available metadata field
|
||||
[ -n "$album" ] && lameopts+=(--tl "$album" )
|
||||
[ -n "$artist" ] && lameopts+=(--ta "$artist")
|
||||
[ -n "$genre" ] && lameopts+=(--tg "$genre")
|
||||
[ -n "$title" ] && lameopts+=(--tt "$title")
|
||||
[ -n "$track" ] && lameopts+=(--tn "$track")
|
||||
[ -n "$year" ] && lameopts+=(--ty "$year")
|
||||
# Extended tags using ID3v2 frames (TXXX for custom/non-standard fields)
|
||||
[ -n "$albumartist" ] && lameopts+=(--tv TPE2="$albumartist")
|
||||
[ -n "$composer" ] && lameopts+=(--tv TCOM="$composer")
|
||||
[ -n "$performer" ] && lameopts+=(--tv TOPE="$performer")
|
||||
@ -18,6 +35,9 @@ encodeFile::mp3() {
|
||||
[ -n "$replaygain_trk" ] \
|
||||
&& lameopts+=(--tv "TXXX=REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
|
||||
[ -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 ))
|
||||
then
|
||||
# If 'rate' is not one of these value, it cannot be encoded to
|
||||
@ -25,6 +45,7 @@ encodeFile::mp3() {
|
||||
# rate to use.
|
||||
if [ -n "${destinationfrequency["$destination"]}" ]
|
||||
then
|
||||
# Target frequency was explicitly set; use that
|
||||
case ${destinationfrequency["$destination"]} in
|
||||
48000) lameopts+=(--resample 48) ;;
|
||||
44100) lameopts+=(--resample 44.1) ;;
|
||||
@ -38,8 +59,10 @@ encodeFile::mp3() {
|
||||
esac
|
||||
elif (( rate > 48000 ))
|
||||
then
|
||||
# Source rate exceeds MP3 maximum; cap at 48kHz
|
||||
lameopts+=(--resample 48)
|
||||
else
|
||||
# Use the source file's own sample rate
|
||||
case $rate in
|
||||
48000) lameopts+=(--resample 48) ;;
|
||||
44100) lameopts+=(--resample 44.1) ;;
|
||||
@ -53,7 +76,11 @@ encodeFile::mp3() {
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
# Append input WAV and output MP3 paths to complete the command
|
||||
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=$(
|
||||
Insert tasks <<-EOInsert
|
||||
key ${fileid}lame$destination
|
||||
@ -63,6 +90,9 @@ encodeFile::mp3() {
|
||||
$(
|
||||
for key in ${!lameopts[@]}
|
||||
do
|
||||
# Escape special characters that could
|
||||
# interfere with bash glob/brace
|
||||
# expansion
|
||||
cleanedopts="${lameopts[key]//\&/\\\&}"
|
||||
cleanedopts="${cleanedopts//\[/\\[}"
|
||||
cleanedopts="${cleanedopts//\]/\\]}"
|
||||
@ -78,6 +108,8 @@ encodeFile::mp3() {
|
||||
ascii ${destinationascii["$destination"]}
|
||||
EOInsert
|
||||
)
|
||||
# Increment parent task's required_by counter so it won't clean up
|
||||
# until all children finish
|
||||
parent_required=$(
|
||||
Select tasks required_by \
|
||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||
@ -85,5 +117,5 @@ encodeFile::mp3() {
|
||||
Update tasks required_by $((++parent_required)) \
|
||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||
progressSpin
|
||||
soxtaskid=''
|
||||
soxtaskid='' # Clear sox task ID so next destination starts fresh
|
||||
}
|
||||
|
||||
@ -1,9 +1,27 @@
|
||||
#!/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() {
|
||||
# Build opusenc VBR encode command
|
||||
opusencopts=(${ionice}opusenc --quiet)
|
||||
opusencopts+=(--bitrate ${destinationquality[$destination]})
|
||||
# Add Forward Error Correction if a packet loss percentage was
|
||||
# configured
|
||||
[ -n "${destinationloss["$destination"]}" ] \
|
||||
&& opusencopts+=(--expect-loss "${destinationloss["$destination"]}")
|
||||
# Embed Ogg comment tags
|
||||
[ -n "$albumartist" ] && opusencopts+=(--comment "ALBUMARTIST=$albumartist")
|
||||
[ -n "$album" ] && opusencopts+=(--comment "ALBUM=$album")
|
||||
[ -n "$artist" ] && opusencopts+=(--artist "$artist")
|
||||
@ -20,6 +38,8 @@ encodeFile::opus() {
|
||||
&& opusencopts+=(--comment) \
|
||||
&& opusencopts+=("REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
|
||||
[ -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 "TRACKTOTAL=${track#*/}")
|
||||
[ -n "$year" ] && opusencopts+=(--comment "DATE=$year")
|
||||
@ -31,6 +51,8 @@ encodeFile::opus() {
|
||||
fileid $destfileid
|
||||
filename $destdir/$destfile.opus
|
||||
$(
|
||||
# Escape special characters that could
|
||||
# interfere with bash glob/brace expansion
|
||||
for key in ${!opusencopts[@]}
|
||||
do
|
||||
cleanedopts="${opusencopts[key]//\&/\\\&}"
|
||||
@ -48,6 +70,8 @@ encodeFile::opus() {
|
||||
ascii ${destinationascii["$destination"]}
|
||||
EOInsert
|
||||
)
|
||||
# Increment parent task's required_by counter so it won't clean up
|
||||
# until all children finish
|
||||
parent_required=$(
|
||||
Select tasks required_by \
|
||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||
|
||||
@ -1,6 +1,23 @@
|
||||
#!/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() {
|
||||
# Build oggenc quality-based encode command
|
||||
# (-Q = quiet, -q = quality level)
|
||||
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 "$album" ] && oggencopts+=(-l "$album")
|
||||
[ -n "$artist" ] && oggencopts+=(-a "$artist")
|
||||
@ -17,6 +34,7 @@ encodeFile::vorbis() {
|
||||
[ -n "$title" ] && oggencopts+=(-t "$title")
|
||||
[ -n "$track" ] && oggencopts+=(-N "$track")
|
||||
[ -n "$year" ] && oggencopts+=(-d "$year")
|
||||
# -o output must come before input for oggenc
|
||||
oggencopts+=(-o "${destinationpath[$destination]}/$destdir/$destfile.ogg" "$tempdir/$tmpfile.wav")
|
||||
encodetaskid=$(
|
||||
Insert tasks <<-EOInsert
|
||||
@ -25,6 +43,8 @@ encodeFile::vorbis() {
|
||||
fileid $destfileid
|
||||
filename $destdir/$destfile.ogg
|
||||
$(
|
||||
# Escape special characters that could
|
||||
# interfere with bash glob/brace expansion
|
||||
for key in ${!oggencopts[@]}
|
||||
do
|
||||
cleanedopts="${oggencopts[key]//\&/\\\&}"
|
||||
@ -42,6 +62,8 @@ encodeFile::vorbis() {
|
||||
ascii ${destinationascii["$destination"]}
|
||||
EOInsert
|
||||
)
|
||||
# Increment parent task's required_by counter so it won't clean up
|
||||
# until all children finish
|
||||
parent_required=$(
|
||||
Select tasks required_by \
|
||||
<<<"id = ${soxtaskid:-$decodetaskid}"
|
||||
|
||||
@ -1,5 +1,22 @@
|
||||
#!/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() {
|
||||
# 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
|
||||
last_seen < $scantime
|
||||
EOWhere
|
||||
|
||||
@ -1,4 +1,18 @@
|
||||
#!/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() {
|
||||
shopt -s extglob
|
||||
string="$1"
|
||||
@ -6,7 +20,7 @@ sanitizeFile() {
|
||||
string="${string//\// }"
|
||||
if (( ${destinationfat32compat[$destination]} ))
|
||||
then
|
||||
# Filenames can't contain:
|
||||
# FAT32 forbids these characters in filenames
|
||||
string=${string//\?/ }
|
||||
string=${string//\\/ }
|
||||
string=${string//</ }
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
cat <<-EODesc
|
||||
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
cat <<-EODesc
|
||||
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
cat <<-EODesc
|
||||
|
||||
@ -14,11 +27,14 @@ setupGeneral() {
|
||||
1 minute load average between <load> and <load>+1 by adjusting
|
||||
concurrency. Initial concurrency will be set to half of that value.
|
||||
EODesc
|
||||
# Check input is an integer
|
||||
# Reused across multiple prompts in this function
|
||||
expr='^[0-9]*$'
|
||||
comeagain() {
|
||||
read \
|
||||
-e \
|
||||
-p'Target load: (integer) ' \
|
||||
# Pre-fill with the existing value when re-running setup
|
||||
${maxload+-i"$maxload"} \
|
||||
value
|
||||
if [ -n "$value" ] && [[ $value =~ $expr ]]
|
||||
@ -26,6 +42,7 @@ setupGeneral() {
|
||||
maxload="$value"
|
||||
else
|
||||
echo "Invalid max-load value: $value" >&2
|
||||
# Recurse until we get a valid value
|
||||
comeagain
|
||||
fi
|
||||
}
|
||||
@ -64,6 +81,7 @@ setupGeneral() {
|
||||
read \
|
||||
-e \
|
||||
-p'Ionice: <1-3> [0-7] ' \
|
||||
# Default to class 3 (idle) when no prior value exists
|
||||
-i"${class:-3} ${niceness}" \
|
||||
class niceness
|
||||
case $class in
|
||||
@ -87,6 +105,7 @@ setupGeneral() {
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
# Best-effort class; niceness 0-7 is mandatory
|
||||
if [ -n "$niceness" ] \
|
||||
&& (( niceness >= 0 && niceness <= 7 ))
|
||||
then
|
||||
@ -98,6 +117,7 @@ setupGeneral() {
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
# Idle class; no niceness level is accepted
|
||||
ionice="ionice -c3 "
|
||||
;;
|
||||
*)
|
||||
@ -115,6 +135,7 @@ setupGeneral() {
|
||||
sqlite) and temporary WAVE files will be created. Note that debug logs
|
||||
(if enabled) will go there too.
|
||||
EODesc
|
||||
# Tab-completion (-e) works here because readline is active
|
||||
read \
|
||||
-e \
|
||||
-i"${tempdir:-$HOME/.atom/tmp}" \
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
(( regen )) && return 0
|
||||
echo "Parameter $1 for destination $destination changed."
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
cat <<-EOStartConf
|
||||
You will now be asked (hopefully) simple questions to help you configure AtOM's
|
||||
@ -7,11 +20,17 @@ behavior.
|
||||
|
||||
Completion is available for prompts asking for a paths or filenames.
|
||||
EOStartConf
|
||||
# Collect all configuration sections in order
|
||||
setupGeneral
|
||||
setupSource
|
||||
setupDestinations
|
||||
# Clear the regex used by validation loops inside setup sub-functions
|
||||
unset expr
|
||||
# Write the newly gathered config to a temp file so the original is
|
||||
# preserved until the user confirms
|
||||
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 \
|
||||
sourcepath \
|
||||
skippeddirectories \
|
||||
@ -37,6 +56,8 @@ Completion is available for prompts asking for a paths or filenames.
|
||||
destinationnoresample \
|
||||
destinationrenamepath \
|
||||
destinationskipmime
|
||||
# Re-declare per-destination variables as associative arrays so
|
||||
# getConfig can populate them with [destinationname]=value entries
|
||||
declare -A \
|
||||
destinationchannels \
|
||||
destinationfat32compat \
|
||||
@ -54,6 +75,8 @@ Completion is available for prompts asking for a paths or filenames.
|
||||
destinationnoresample \
|
||||
destinationrenamepath \
|
||||
destinationskipmime
|
||||
# Point getConfig at the temp file so the review reflects exactly what
|
||||
# would be written, not the old on-disk config
|
||||
oldcffile="$cffile"
|
||||
cffile="$cffile".tmp
|
||||
getConfig
|
||||
@ -61,6 +84,7 @@ Completion is available for prompts asking for a paths or filenames.
|
||||
echo $'Please review your new configuration:\n'
|
||||
printConfig
|
||||
}| less -F -e
|
||||
# Restore the original config path before deciding whether to commit
|
||||
cffile="$oldcffile"
|
||||
read -p'Write config file? [y/N] ' do_write
|
||||
case $do_write in
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
#!/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() {
|
||||
cat <<-EODesc
|
||||
|
||||
@ -18,6 +31,7 @@ setupSource() {
|
||||
-i"${sourcepath:-/var/lib/mpd/music}" \
|
||||
-p'Music collection (<TAB> for completion): ' \
|
||||
sourcepath
|
||||
# Reject paths that don't exist; AtOM doesn't create the source
|
||||
if ! [ -d "$sourcepath" ]
|
||||
then
|
||||
echo "$sourcepath does not exist or is not a" \
|
||||
@ -34,12 +48,17 @@ setupSource() {
|
||||
|
||||
This prompt will loop until an empty string is encountered.
|
||||
EODesc
|
||||
# Change into the source directory so readline tab-completion resolves
|
||||
# relative paths correctly while the user types skip entries
|
||||
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[@]}
|
||||
for (( i=0 ; 1 ; i++ ))
|
||||
do
|
||||
read \
|
||||
-e \
|
||||
# Pre-fill with the existing entry at this index, if any
|
||||
${skippeddirectories[i]+-i"${skippeddirectories[i]}"}\
|
||||
-p'Skip: ' \
|
||||
value
|
||||
@ -48,11 +67,14 @@ setupSource() {
|
||||
skippeddirectories[i]="$value"
|
||||
elif (( i < count ))
|
||||
then
|
||||
# Blank input on a previously set entry removes it
|
||||
unset skippeddirectories[i]
|
||||
else
|
||||
# Blank input beyond the old list signals end of input
|
||||
break
|
||||
fi
|
||||
done
|
||||
unset count
|
||||
# Return to the prior directory; output suppressed to keep the UI clean
|
||||
cd - >/dev/null
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user