diff --git a/atom b/atom index 9f6d926..97f0688 100755 --- a/atom +++ b/atom @@ -1,5 +1,42 @@ #!/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 OPTERR=0 while getopts ':c:l:T:F:hD' opt @@ -38,11 +75,528 @@ do done #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 + ) + case $(( ++count % 4 )) in + 0) echo -ne '\r|' ;; + 1) echo -ne '\r/' ;; + 2) echo -en '\r-' ;; + 3) echo -ne '\r\\' ;; + esac + done < <( + find "$sourcepath" -type f -printf "%T@ %s %P\n" + ) + echo 'COMMIT;' >&3 + echo -e "\r$count files found." +} + +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() { @@ -127,4 +681,61 @@ transcodeLauncher() { #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 <