701 lines
12 KiB
Bash
Executable File
701 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
## Define exit codes
|
|
# General config errors [10-19]
|
|
ELOAD=10
|
|
EINTERVAL=11
|
|
ENOCFG=19
|
|
# Source cofig errors [20-29]
|
|
# Destination config errors [30-49]
|
|
EFORMAT=30
|
|
ECHANNEL=31
|
|
EFMTINVPARM=49
|
|
|
|
# config structures
|
|
declare -A \
|
|
destinationchannels \
|
|
destinationcopymime \
|
|
destinationformat \
|
|
destinationfrequency \
|
|
destinationid \
|
|
destinationpath \
|
|
destinationquality \
|
|
destinationrename \
|
|
destinationrenamepath \
|
|
destinationskipmime \
|
|
|| {
|
|
echo "Check your Bash version. You need >= 4.0" >&2
|
|
exit $EBASHVERS
|
|
}
|
|
|
|
declare -r \
|
|
DOCDIR=./doc \
|
|
SHAREDIR=./share
|
|
declare -r \
|
|
exampleconf=$DOCDIR/example.cfg \
|
|
schema=$SHAREDIR/schema.sql \
|
|
\
|
|
oldIFS="$IFS"
|
|
|
|
#parse arguments
|
|
#parse config
|
|
getConfigGeneral() {
|
|
case $key in
|
|
'max-load')
|
|
expr='^[0-9]*$'
|
|
if [[ $value =~ $expr ]]
|
|
then
|
|
maxload="$value"
|
|
else
|
|
echo "Invalid max-load value: $value" >&2
|
|
exit $ELOAD
|
|
fi
|
|
unset expr
|
|
;;
|
|
'load-interval')
|
|
expr='^[0-9]*$'
|
|
if [[ $value =~ $expr ]]
|
|
then
|
|
loadinterval="$value"
|
|
else
|
|
echo "Invalid load-interval value: $value" >&2
|
|
exit $EINTERVAL
|
|
fi
|
|
unset expr
|
|
;;
|
|
'temporary-directory')
|
|
tempdir="$value"
|
|
;;
|
|
'database')
|
|
database="$value"
|
|
;;
|
|
debug)
|
|
#unimplemented
|
|
;;
|
|
esac
|
|
}
|
|
|
|
getConfigSource() {
|
|
case "$key" in
|
|
'path')
|
|
sourcepath="$value"
|
|
;;
|
|
'id3charset')
|
|
sourceid3charset="$value"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
getConfigDestination() {
|
|
case "$key" in
|
|
'path')
|
|
destinationpath["$destination"]="$value"
|
|
;;
|
|
'format')
|
|
case "$value" in
|
|
'mp3')
|
|
destinationformat["$destination"]=mp3
|
|
;;
|
|
'vorbis')
|
|
destinationformat["$destination"]=vorbis
|
|
;;
|
|
*)
|
|
echo "Unsupported destination format: $value" >2&
|
|
exit $EFORMAT
|
|
;;
|
|
esac
|
|
;;
|
|
'quality')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid quality value: $value" >&2
|
|
exit $EQUALITY
|
|
fi
|
|
unset expr
|
|
case "${destinationformat["$destination"]}" in
|
|
'vorbis')
|
|
destinationquality["$destination"]="$value"
|
|
;;
|
|
*)
|
|
echo "$Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
|
exit $EFMTINVPARM
|
|
;;
|
|
esac
|
|
;;
|
|
'bitrate')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid bitrate value: $value" >&2
|
|
exit $EQUALITY
|
|
fi
|
|
unset expr
|
|
case "${destinationformat["$destination"]}" in
|
|
'mp3')
|
|
destinationquality["$destination"]="$value"
|
|
;;
|
|
*)
|
|
echo "$Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
|
exit $EFMTINVPARM
|
|
;;
|
|
esac
|
|
;;
|
|
'channels')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid channel count: $value" >&2
|
|
exit $ECHANNEL
|
|
fi
|
|
unset expr
|
|
destinationchannels["$destination"]=$value
|
|
;;
|
|
'frequency')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid frequency value: $value" >&2
|
|
exit $ECHANNEL
|
|
fi
|
|
unset expr
|
|
destinationfrequency["$destination"]=$value
|
|
;;
|
|
'rename')
|
|
case "$value" in
|
|
*/*)
|
|
destinationrenamepath["$destination"]="${value%/*}"
|
|
;;
|
|
esac
|
|
destinationrename["$destination"]="${value##*/}"
|
|
;;
|
|
'skip_mime-type')
|
|
destinationskipmime[$destination]="${destinationskipmime[$destination]:+${destinationskipmime[$destination]}|}$value"
|
|
;;
|
|
'copy_mime-type')
|
|
destinationcopymime[$destination]="${destinationcopymime[$destination]:+${destinationcopymime[$destination]}|}$value"
|
|
;;
|
|
esac
|
|
}
|
|
getConfig() {
|
|
while read key value
|
|
do
|
|
case $key in
|
|
'#'*)
|
|
#comment
|
|
;;
|
|
'')
|
|
#empty line
|
|
;;
|
|
'[general]')
|
|
context=General
|
|
;;
|
|
'[source]')
|
|
context=Source
|
|
;;
|
|
\[*\])
|
|
context=Destination
|
|
destination="${key#[}"
|
|
destination="${destination%]}"
|
|
;;
|
|
*)
|
|
getConfig$context
|
|
;;
|
|
esac
|
|
done < ~/.atom/atom.cfg
|
|
}
|
|
|
|
#check sanity
|
|
|
|
openDatabase() {
|
|
if [ ! -d "$tempdir" ]
|
|
then
|
|
mkdir -p "$tempdir"
|
|
fi
|
|
mkfifo "$tempdir"/sqlite.{in,out}
|
|
if [ ! -f "$database" ]
|
|
then
|
|
if [ ! -d "${database%/*}" ]
|
|
then
|
|
mkdir -p "${database%/*}"
|
|
fi
|
|
sqlite3 "$database" < $schema
|
|
fi
|
|
sqlite3 "$database" \
|
|
< "$tempdir/sqlite.in" \
|
|
> "$tempdir/sqlite.out" &
|
|
exec 3> "$tempdir"/sqlite.in
|
|
exec 4< "$tempdir"/sqlite.out
|
|
}
|
|
|
|
closeDatabase() {
|
|
exec 3>&-
|
|
exec 4<&-
|
|
rm "$tempdir"/sqlite.{in,out}
|
|
}
|
|
|
|
Select() {
|
|
#Select table [col1 [col2 [..]]] < WHERE_key WHERE_operator WHERE_value
|
|
# [WHERE_key WHERE_operator WHERE_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
col \
|
|
columns \
|
|
operator \
|
|
results \
|
|
where_statement
|
|
shift
|
|
for col
|
|
do
|
|
(( ${#columns} )) && columns+=','
|
|
columns+="$col"
|
|
done
|
|
while read key operator value
|
|
do
|
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
|
where_statement+=( "$key $operator "'"'"${value//\"/\\\"}"'"' )
|
|
done
|
|
echo "SELECT IFNULL(" \
|
|
"(SELECT $columns FROM $table" \
|
|
"WHERE ${where_statement[@]})" \
|
|
",'SQL::Select:not found'" \
|
|
");" >&3
|
|
read -u 4 results
|
|
if ! [[ $results == "SQL::Select:not found" ]]
|
|
then
|
|
echo "$results"
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
Insert() {
|
|
#Insert table [no_id] < key value
|
|
# [key value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
no_id="${2:-0}" \
|
|
insert_keys \
|
|
insert_values \
|
|
results
|
|
while read key value
|
|
do
|
|
(( ${#insert_keys} )) && insert_keys+=","
|
|
insert_keys+='`'"$key"'`'
|
|
(( ${#insert_values} )) && insert_values+=","
|
|
if [[ $value == NULL ]]
|
|
then
|
|
insert_values+="NULL"
|
|
else
|
|
insert_values+='"'"${value//\"/\\\"}"'"'
|
|
fi
|
|
done
|
|
echo "INSERT INTO $table" \
|
|
"( $insert_keys )" \
|
|
"VALUES" \
|
|
"( $insert_values );" >&3
|
|
(( no_id )) || {
|
|
echo 'SELECT LAST_INSERT_ROWID();' >&3
|
|
read -u 4 results
|
|
echo "$results"
|
|
}
|
|
}
|
|
Update(){
|
|
#Update table set_key set_value [set_key set_value […]] < where_key where_operator where_value
|
|
# [where_key where_operator where_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
key \
|
|
argument \
|
|
operator \
|
|
value \
|
|
set_statement \
|
|
where_keys \
|
|
where_values \
|
|
what \
|
|
where_statement \
|
|
results
|
|
shift
|
|
what=key
|
|
for argument
|
|
do
|
|
case $what in
|
|
key)
|
|
set_statement="${set_statement:+${set_statement},}\`$argument\`"
|
|
what=value
|
|
;;
|
|
value)
|
|
set_statement="${set_statement}="'"'"${argument//\"/\\\"}"'"'
|
|
what=key
|
|
;;
|
|
esac
|
|
done
|
|
while read key operator value
|
|
do
|
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
|
if [[ $value == NULL ]]
|
|
then
|
|
where_statement+=( "$key is NULL" )
|
|
else
|
|
where_statement+=( "$key $operator "'"'"${value//\"/\\\"}"'"' )
|
|
fi
|
|
done
|
|
echo "UPDATE '$table' SET" \
|
|
"$set_statement" \
|
|
"WHERE" \
|
|
"${where_statement[@]}" \
|
|
";" >&3
|
|
}
|
|
InsertIfUnset() {
|
|
#InsertIfUnset table [no_id] < key value \n key value
|
|
local \
|
|
table="$1" \
|
|
no_id="${2:-0}" \
|
|
column \
|
|
key \
|
|
keys \
|
|
results \
|
|
value \
|
|
values
|
|
while read key value
|
|
do
|
|
keys+=( "$key" )
|
|
values+=( "$value" )
|
|
done
|
|
if (( no_id ))
|
|
then
|
|
column="${keys[0]}"
|
|
else
|
|
column='id'
|
|
fi
|
|
if ! results=$(
|
|
Select "$table" "$column" < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" = "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
then
|
|
results=$(
|
|
Insert "$table" < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
fi
|
|
echo "$results"
|
|
}
|
|
InsertOrUpdate() {
|
|
#InsertOrUpdate table set_key set_value [set_key set_value […]] < where_key where_value
|
|
# [where_key where_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
argument \
|
|
key \
|
|
keys \
|
|
set_keys \
|
|
set_values \
|
|
value \
|
|
values \
|
|
what \
|
|
results
|
|
shift
|
|
what=key
|
|
for argument
|
|
do
|
|
case $what in
|
|
key)
|
|
set_keys+=( "$argument" )
|
|
what=value
|
|
;;
|
|
value)
|
|
set_values+=( "$argument" )
|
|
what=key
|
|
;;
|
|
esac
|
|
done
|
|
while read key value
|
|
do
|
|
keys+=( "$key" )
|
|
values+=( "$value" )
|
|
done
|
|
if results=$(
|
|
Select "$table" id < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" = "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
then
|
|
Update "$table" "$@" < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" = "${values[$key]}"
|
|
done
|
|
)
|
|
else
|
|
results=$(
|
|
Insert "$table" < <(
|
|
for key in ${!set_keys[@]}
|
|
do
|
|
echo "${set_keys[$key]}" "${set_values[$key]}"
|
|
done
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
fi
|
|
echo "$results"
|
|
}
|
|
Delete() {
|
|
#Delete table < where_key where_operator where_value
|
|
# [where_key where_operator where_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
key \
|
|
operator \
|
|
value \
|
|
where_statement \
|
|
results
|
|
while read key operator value
|
|
do
|
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
|
if [[ $value == NULL ]]
|
|
then
|
|
where_statement+=( "$key is NULL" )
|
|
else
|
|
where_statement+=( "$key $operator "'"'"${value//\"/\\\"}"'"' )
|
|
fi
|
|
done
|
|
echo "DELETE from $table WHERE ${where_statement[@]};" >&3
|
|
}
|
|
|
|
createDestinations() {
|
|
for destination in ${!destinationpath[@]}
|
|
do
|
|
if ! [ -d "${destinationpath["$destination"]}" ]
|
|
then
|
|
if ! mkdir -p "${destinationpath["$destination"]}"
|
|
then
|
|
echo "$destination: Could not create ${destinationpath["$destination"]}!"
|
|
exit $EINVDEST
|
|
fi
|
|
fi
|
|
destinationid["$destination"]=$(
|
|
InsertIfUnset destinations <<<"name $destination"
|
|
)
|
|
done
|
|
}
|
|
|
|
getFiles() {
|
|
scantime=$(date +%s)
|
|
# We probably have thousands of files, don't waste time on disk writes
|
|
echo 'BEGIN TRANSACTION;' >&3
|
|
while read time size filename
|
|
do
|
|
sourcefileid=$(
|
|
InsertOrUpdate source_files \
|
|
last_change ${time%.*} \
|
|
size $size \
|
|
last_seen $scantime \
|
|
<<-EOWhere
|
|
filename $filename
|
|
EOWhere
|
|
)
|
|
echo -ne '\r'$sourcefileid
|
|
done < <(
|
|
find "$sourcepath" -type f -printf "%T@ %s %P\n"
|
|
)
|
|
echo 'COMMIT;' >&3
|
|
echo
|
|
}
|
|
|
|
updateMimes() {
|
|
Update mime_actions action 1 <<<"action != 1"
|
|
for destination in ${!destinationskipmime[@]}
|
|
do
|
|
IFS='|'
|
|
for mime_type in ${destinationskipmime["$destination"]}
|
|
do
|
|
IFS="$oldIFS"
|
|
Update mime_type_actions action 0 >/dev/null < <(
|
|
cat <<-EOWhere
|
|
destination_id = ${destinationid["$destination"]}
|
|
mime_text LIKE ${mime_type//*/%}
|
|
EOWhere
|
|
)
|
|
done
|
|
done
|
|
for destination in ${!destinationcopymime[@]}
|
|
do
|
|
IFS='|'
|
|
for mime_type in ${destinationcopymime["$destination"]}
|
|
do
|
|
IFS="$oldIFS"
|
|
Update mime_type_actions action 2 >/dev/null < <(
|
|
cat <<-EOWhere
|
|
destination_id = ${destinationid["$destination"]}
|
|
mime_text LIKE ${mime_type//*/%}
|
|
EOWhere
|
|
)
|
|
done
|
|
done
|
|
}
|
|
|
|
removeObsoleteFiles() {
|
|
Delete source_files <<-EOWhere
|
|
last_seen < $scantime
|
|
EOWhere
|
|
}
|
|
|
|
getType() {
|
|
:
|
|
}
|
|
|
|
getInfos::MP3() {
|
|
:
|
|
}
|
|
|
|
getInfos::Ogg() {
|
|
:
|
|
}
|
|
|
|
getInfos::FLAC() {
|
|
:
|
|
}
|
|
|
|
getInfos::MPC() {
|
|
:
|
|
}
|
|
|
|
getTags() {
|
|
#getType
|
|
#getInfos::<type>
|
|
:
|
|
}
|
|
|
|
encodeFile::MP3() {
|
|
#lame
|
|
:
|
|
}
|
|
|
|
encodeFile::Ogg() {
|
|
#oggenc
|
|
:
|
|
}
|
|
|
|
transcodeFile() {
|
|
#sox -> wav
|
|
for format in $targets["format"]
|
|
do
|
|
#encodeFile::$format
|
|
:
|
|
done
|
|
#signal and of encoding to parent
|
|
}
|
|
|
|
checkFinished() {
|
|
#retrieve info from finished transcodeFile()
|
|
#update counters / metadata
|
|
:
|
|
}
|
|
|
|
checkLoad() {
|
|
#if load > threshold
|
|
# decrease concurrency
|
|
#elif load < threshold
|
|
# increase concurrency
|
|
#fi
|
|
:
|
|
}
|
|
|
|
readUserInput() {
|
|
#read + / - / q(uit) / p(ause)
|
|
#initiate shutdown / check load threshold / SIGSTOP all children / SIGCONT all children
|
|
:
|
|
}
|
|
|
|
transcodeLauncher() {
|
|
checkLoad
|
|
checkFinished
|
|
#until running processes < max processes
|
|
#do
|
|
checkLoad
|
|
checkFinished
|
|
readUserInput
|
|
#done
|
|
transcodeFile &
|
|
#update counter / metadata
|
|
}
|
|
|
|
#UI
|
|
|
|
if [ ! -f ~/.atom/atom.cfg ]
|
|
then
|
|
if [ ! -d ~/.atom ]
|
|
then
|
|
mkdir -p ~/.atom
|
|
fi
|
|
sed "s:%HOME%:$HOME:" "$exampleconf" > ~/.atom/atom.cfg
|
|
cat >&2 <<-EOCfgNotice
|
|
No configuration file found!
|
|
An example file has been created as ~/.atom/atom.cfg.
|
|
You should change it to your likings using you favorite editor.
|
|
|
|
Bailing out.
|
|
EOCfgNotice
|
|
exit $ENOCFG
|
|
fi
|
|
getConfig
|
|
{
|
|
cat <<EOF
|
|
General|Load|$maxload
|
|
|Load Interval|$loadinterval
|
|
|Temp Dir|$tempdir
|
|
|Database|$database
|
|
|Debug|$debug
|
|
Source|Path|$sourcepath
|
|
|ID3 Charset|$sourceid3charset
|
|
EOF
|
|
for destination in ${!destinationpath[@]}
|
|
do
|
|
cat <<EOF
|
|
$destination|Path|${destinationpath[$destination]}
|
|
|Format|${destinationformat[$destination]}
|
|
|Quality|${destinationquality[$destination]}
|
|
|Channels|${destinationchannels[$destination]}
|
|
|Frequency|${destinationfrequency[$destination]}
|
|
|Path Change|${destinationrenamepath[$destination]}
|
|
|File Rename|${destinationrename[$destination]}
|
|
EOF
|
|
echo "|Skipped mime-type|${destinationskipmime[$destination]//\|/
|
|
|Skipped mime-type|}"
|
|
echo "|Copied mime-type|${destinationcopymime[$destination]//\|/
|
|
|Copied mime-type|}"
|
|
done
|
|
}|column -t -s'|' -n
|
|
|
|
openDatabase
|
|
|
|
createDestinations
|
|
|
|
getFiles
|
|
|
|
updateMimes
|
|
|
|
removeObsoleteFiles
|
|
|
|
closeDatabase
|
|
|
|
# vim:set ts=8 sw=8:
|