Merge forgotten changes on netbook

Conflicts:
	atom
This commit is contained in:
Vincent Riquer 2013-02-27 23:19:24 +01:00
commit 4462c6d58f
3 changed files with 726 additions and 1 deletions

613
atom
View File

@ -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 <<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:

43
doc/example.cfg Normal file
View File

@ -0,0 +1,43 @@
[general]
max-load 6
load-interval 30
temporary-directory %HOME%/.atom/tmp
database %HOME%/.atom/atom.db
debug 0
[source]
path /var/lib/mpd/music
id3charset iso-8859-15
[Ogg]
path /mnt/Musique-OggQ2
format vorbis
quality 1
channels 2
frequency 44100
# you should not skip or copy application/octet-stream, they could be something
# similar to "Audio file with ID3 version 2.4.0, unsynchronized frames"
copy_mime-type image/*
copy_mime-type text/*
[MP3]
path /mnt/Musique-mp3.test
format mp3
bitrate 96
# rename file, path unchanged
rename %{track}--%{artist}-%{title}
# change the whole filepath
#rename %{genre}/%{albumartist}/%{year}-%{album}/%{track}--%{artist}-%{title}
skip_mime-type image/*
skip_mime-type text/*
[asterisk]
path /mnt/Musique-asterisk
format vorbis
channels 1
frequency 8000
skip_mime-type image/*
skip_mime-type text/*

71
share/schema.sql Normal file
View File

@ -0,0 +1,71 @@
BEGIN TRANSACTION;
CREATE TABLE source_files (
id INTEGER PRIMARY KEY,
filename TEXT UNIQUE NOT NULL,
size INTEGER NOT NULL,
hash TEXT,
mime_type INTEGER,
last_change INTEGER NOT NULL DEFAULT (strftime('%s','now')),
last_seen INTEGER NOT NULL DEFAULT (strftime('%s','now')),
FOREIGN KEY (mime_type) REFERENCES mime_types(id)
);
CREATE TABLE destinations (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE destination_files (
id INTEGER PRIMARY KEY,
filename TEXT,
last_change INTEGER NOT NULL DEFAULT 0,
source_file_id INTEGER,
destination_id INTEGER,
FOREIGN KEY (source_file_id) REFERENCES source_files(id),
FOREIGN KEY (destination_id) REFERENCES destinations(id)
);
CREATE TABLE mime_types (
id INTEGER PRIMARY KEY,
mime_text TEXT UNIQUE NOT NULL
);
CREATE TABLE mime_actions (
id INTEGER PRIMARY KEY,
mime_type INTEGER,
destination_id INTEGER,
action INTEGER DEFAULT 1,
FOREIGN KEY (mime_type) REFERENCES mime_types(id)
FOREIGN KEY (destination_id) REFERENCES destinations(id)
);
CREATE VIEW mime_type_actions AS
SELECT
mime_types.id,mime_types.mime_text,
mime_actions.destination_id,mime_actions.action
FROM mime_types INNER JOIN mime_actions
ON mime_actions.mime_type = mime_types.id;
CREATE TRIGGER update_mime_actions INSTEAD OF UPDATE OF action ON mime_type_actions
BEGIN
UPDATE mime_actions SET action=new.action WHERE mime_type=old.id;
END;
CREATE TRIGGER create_mime_actions AFTER INSERT ON mime_types
BEGIN
INSERT INTO mime_actions (mime_type,destination_id)
SELECT mime_types.id,destinations.id
FROM mime_types INNER JOIN destinations
WHERE mime_types.id=new.id;
END;
CREATE INDEX sourcefiles_by_name ON source_files (filename,id);
CREATE TRIGGER create_destinations AFTER INSERT ON source_files
BEGIN
INSERT INTO destination_files (source_file_id,destination_id)
SELECT source_files.id,destinations.id FROM source_files
INNER JOIN destinations
WHERE source_files.id=new.id;
END;
CREATE TRIGGER delete_destinations_files AFTER DELETE ON source_files
BEGIN
UPDATE destination_files SET source_file_id=NULL WHERE source_file_id=old.id;
END;
COMMIT;