1 contributor
#!/bin/bash
set -e
declare -A data=(
[/sim/description]=text
[/sim/long-description]=text
[/sim/author]=text
[/sim/flight-model]=text
[/sim/type]=text
[/sim/model/path]=text
)
fgaddon_svn=https://svn.code.sf.net/p/flightgear/fgaddon/trunk/Aircraft
fgaddon_path=$HOME/.fgfs/flightgear-fgaddon/Aircraft
database=${DB:-$0.db}
#locale=fr
test -r "$0.conf" && source $0.conf && echo config red
aircrafts=$(mktemp --dry-run /dev/shm/Aircraft-XXXXXXXXX)
aircraft=$(mktemp --dry-run /dev/shm/aircraft-XXXXXXX)
setxml=$(mktemp --dry-run /dev/shm/setxml-XXXXXXXX)
in_ram_database=$(mktemp --dry-run /dev/shm/XXXXXXX)
function xmlgetnext () {
local IFS='>'
read -d '<' TAG VALUE
# by design, the first TAG/VALUE pair is empty
# to avoid infinite loops at end of file parsing we return an error
# the next time we find an empty TAG
if test -z "$TAG"; then
test ${xmlgetnext_empty_tag:-0} -gt 0 && return 1
xmlgetnext_empty_tag=$(( xmlgetnext_empty_tag + 1 ))
fi
# process $TAG only if necessary
local _TAG=$(printf '%q' $TAG)
if test ${_TAG:0:1} = '$'; then
TAG=$(tr '\n' ' ' <<< $TAG | sed 's/ */ /g; s/ *$//')
fi
}
rm -f /dev/shm/sqlite_request
function sqlite_request () {
local delimiter=';'
test ${1:0:1} == '.' && delimiter=''
echo "${1}${delimiter}" >> /dev/shm/sqlite_request
if ! sqlite3 "$in_ram_database" <<< "$1"; then
register_state
fi
}
function xmlremovecomments () {
sed -ri 's/<(!--|script>)/\n&/;s/(<\/script|--)>/&\n/' $setxml
sed -ri '/<(script>|!--).*(<\/script|--)>/d;/<(script>|!--)/,/(<\/script|--)>/d' $setxml
sed -i 's/\xef\xbb\xbf//' $setxml # removes BOM
}
function trap_break () {
trap '' INT
echo "stop requested"
register_state
}
function trap_exit () {
trapped_rc=$?
trap '' INT
rm -f $aircrafts $aircraft $setxml
if test ! -e $in_ram_database; then
exit
fi
test $trapped_rc -ne 0 && register_state
echo "updating installation status"
for ac in $(sqlite_request 'select printf("%i:%s/%s", aircrafts.id, aircrafts.name, setxml.file)
from aircrafts inner join setxml
where aircrafts.id = setxml.variantof and setxml.installed != 0;'); do
ac_path=${ac#*:}
if test ! -e $fgaddon_path/$ac_path-set.xml; then
sqlite_request "update setxml set installed = 0 where file = '${ac_path#*/}' and variantof = ${ac%:*}"
fi
done
for ac in $fgaddon_path/*/*-set.xml; do
ac=${ac/$fgaddon_path}
sx=${ac##*/}
ac=${ac%/*}
if test -d $fgaddon_path/$ac/.svn; then
install_type=1
elif test -d $fgaddon_path/$ac/.git; then
install_type=2
else
install_type=3
fi
sqlite_request "update setxml set installed = $install_type
where exists (
select 1
from aircrafts
where name = '${ac/\/}' and setxml.variantof = id
)"
done
local missing_setxml=$(sqlite_request "select printf(' - %s', name)
from aircrafts
where id not in (select variantof from setxml)")
if test -n "$missing_setxml"; then
echo -e "missing setxml config for :\n$missing_setxml"
fi
local missing_model=$(sqlite_request 'select count(setxml.file)
from aircrafts inner join setxml
where aircrafts.id = setxml.variantof and setxml.`/sim/model/path` = ""')
if test $missing_model -gt 0; then
echo "$missing_model aircrafts without /sim/model/path information"
if test $missing_model -le 10; then
echo "aircrafts without /sim/model/path information:"
sqlite_request 'select printf(" - %s/%s", aircrafts.name, setxml.file)
from aircrafts inner join setxml
where aircrafts.id = setxml.variantof and setxml.`/sim/model/path` = ""'
fi
fi
if test -r "$database" && md5sum $in_ram_database | sed "s,$in_ram_database,$database," | md5sum --status -c -; then
rm -f $in_ram_database
echo "no changes in $database"
elif test -w "$database"; then
sqlite_request "vacuum"
mv -f $in_ram_database "$database"
echo "database $database updated"
elif ! test -e "$database"; then
mv $in_ram_database "$database"
echo "database $database created"
else
rm -f $in_ram_database
echo "nothing can be done with $database !"
fi
}
function register_state () {
sqlite_request "drop table if exists recover_rev"
sqlite_request "create table recover_rev (
revkey text,
revision integer,
revauthor text,
revdate integer
)"
for revkey in ${!revindex[@]}; do
sqlite_request "insert into recover_rev values (
'$revkey',
${revindex[$revkey]:-0},
'${revauthor[$revkey]}',
${revdate[$revkey]:-0}
)"
done
sqlite_request "drop table if exists recover_setxmlmodified"
sqlite_request "create table if not exists recover_setxmlmodified (
sx text
)"
for sx in ${!setxmlmodified[@]}; do
sqlite_request "insert into recover_setxmlmodified values (
'$sx'
)"
done
exit
}
function update_database () {
dbupdate=$(sqlite_request "select revision from aircrafts where name is '${ac:1}'")
if test -z "$dbupdate"; then
sqlite_request "insert into aircrafts (name, revision, date, author)
values ('${ac:1}', ${revindex[$ac]}, ${revdate[$ac]}, '${revauthor[$ac]}')"
elif test $dbupdate -lt ${revindex[$ac]}; then
sqlite_request "update aircrafts set
revision = ${revindex[$ac]},
author = '${revauthor[$ac]}',
date = ${revdate[$ac]}
where name is '${ac:1}'"
fi
id=$(sqlite_request "select id from aircrafts where name is '${ac:1}'")
echo "[ ${#revindex[@]} ] ${ac:1}"
unset revindex[$ac]
for sx in ${!setxmlmodified[@]}; do
unset include include_rootpath
[[ "$sx" =~ ^"${ac:1}/" ]] || continue
for col in ${!data[@]}; do
data[$col]=
done
sx=${sx#*/}
echo " -> $sx"
if ! svn cat $fgaddon_svn/${ac:1}/$sx-set.xml > $setxml; then
register_state
fi
xmlremovecomments
unset xmlgetnext_empty_tag property
while xmlgetnext; do
case "${TAG:0:1}" in
''|'?'|'!')
continue;;
/)
property=${property%/*};;
*)
if test "${TAG: -1}" != '/'; then
property+=/${TAG%% *}
fi;;
esac
if [[ "$TAG" =~ ^"PropertyList include=" ]]; then
include_rootpath=${include%/*}
test $include = $include_rootpath && unset include_rootpath
eval $(echo ${TAG#* })
[[ "$include" =~ ^Aircraft/Generic/ ]] && unset include include_rootpath && continue
if [[ "$include" =~ ^'../' ]]; then
if test -n "$include_rootpath"; then
if [[ "$include_rootpath" =~ '/' ]]; then
include_rootpath=${include_rootpath%/*}
else
unset include_rootpath
fi
else
ac_save=$ac
unset ac
fi
include=${include/\.\.\/}
fi
if ! svn cat $fgaddon_svn/${ac:1}/${include_rootpath:+$include_rootpath/}$include >> $setxml; then
register_state
fi
xmlremovecomments
fi
if [[ "$property" = /PropertyList@($data_pattern) ]]; then
if test -z "${data[${property/\/PropertyList}]}"; then
eval "data[${property/\/PropertyList}]=\"${VALUE//\"/\\\"}\""
data[${property/\/PropertyList}]=$(tr '\n' ' ' <<< ${data[${property/\/PropertyList}]} | sed -r 's/^\s*//;s/\s+/ /g;s/\s*$//')
fi
fi
# continue parsing (while loop) until everything's found
for col in ${!data[@]}; do
test -z "${data[$col]}" && continue 2
done
break # everything's found
done < $setxml
if eval "test -z \"$data_test_null\""; then
echo "WARNING: no info found, skipping"
continue
fi
known=$(sqlite_request "select variantof from setxml where file is '$sx'")
if test -n "$known"; then
for col in ${!data[@]}; do
dbvalue=$(sqlite_request "select \`$col\`
from setxml
where file is '$sx' and variantof = $known")
if test "$dbvalue" != "${data[$col]}" -a -n "${data[$col]}"; then
sqlite_request "update setxml
set \`$col\` = '${data[$col]//\'/\'\'}'
where file is '$sx' and variantof = $known"
fi
done
else
values="'$sx', $id, "
for col in ${!data[@]}; do
values+="'${data[$col]//\'/\'\'}', "
done
values+=0
sqlite_request "insert into setxml values ($values)"
fi
test -n "$ac_save" && ac=$ac_save
unset setxmlmodified[${ac:1}/$sx]
done
}
function apply_revision () {
for ac in "${!revindex[@]}"; do
update_database
if test -d $fgaddon_path/${ac:1}/.svn \
&& test "$(svn info --show-item=url $fgaddon_path/${ac:1})" != "$fgaddon_svn/${ac:1}" \
|| test -d $fgaddon_path/${ac:1} -a ! -d $fgaddon_path/${ac:1}/.svn; then
echo "INFO: local ${ac:1} installed out from repo" >&2
fi
done
}
trap trap_break INT
trap trap_exit EXIT
stty -echoctl
declare -A revindex revauthor revdate setxmlmodified revpath
data_pattern=$(printf "%s|" ${!data[@]})
data_pattern=${data_pattern:0:-1}
data_test_null=$(printf '${data[%s]}' ${!data[@]})
if test -e $database; then
cp $database $in_ram_database
sql_cols=$(sqlite_request "pragma table_info(setxml)" | awk -F'|' '{printf("%s %s ", $2, $3)}')
script_cols="file text variantof integer "
for col in ${!data[@]}; do
script_cols+="$col ${data["$col"]} "
done
script_cols+="installed integer " # last space is important
if test "$sql_cols" != "$script_cols"; then
echo "ALERT: datbase version mismatch !"
exit 1
fi
if sqlite_request '.tables' | grep -q 'recover_' && test -z "$1"; then
echo "recovering from previous saved state"
eval $(sqlite_request "select printf('revindex[%s]=%u;revauthor[%s]=%s;revdate[%s]=%u;',
revkey, revision,
revkey, revauthor,
revkey, revdate)
from recover_rev")
eval $(sqlite_request "select printf('setxmlmodified[%s]=1;', sx)
from recover_setxmlmodified")
sqlite_request "drop table recover_rev"
sqlite_request "drop table recover_setxmlmodified"
apply_revision
exit
fi
fi
sqlite_request "create table if not exists aircrafts (
id integer primary key,
name text,
revision integer,
date integer,
author text)"
sqlite_request "create table if not exists setxml (
file text,
variantof integer,
$(for col in ${!data[@]}; do printf "'%s' %s, " $col ${data[$col]}; done)
installed integer)"
latest_revision=$(( $(sqlite_request "select max(revision) from aircrafts") + 1 ))
# for debugging purpose
if test -n "$2"; then
ac=_${1%/*}
revindex[$ac]=1
revdate[$ac]=0
revauthor[$ac]=foobar
setxmlmodified[${ac:1}/${1#*/}]=1
set -x
update_database
set +x
exit
elif test -n "$1"; then
ac=_${1%/*}
eval $(sqlite_request "select printf('revindex[_%s]=%s;revdate[_%s]=%i;revauthor[_%s]=%s;',
name, revision,
name, date,
name, author)
from aircrafts
where name = '${ac:1}'")
setxmlmodified[${ac:1}/${1#*/}]=1
if test -z "${revindex[$ac]}"; then
echo "aircraft ${ac:1} not found"
rm $in_ram_database
exit
fi
update_database
########################################
# TO KEEP SETXML uncomment below lines:
########################################
# mkdir -p /dev/shm/aircrafts/${1%/*}
# ln -f $setxml /dev/shm/aircrafts/$1
########################################
exit
fi
echo "downloading FGADDON history from revision ${latest_revision:-0}"
svn log --revision ${latest_revision:-0}:HEAD --xml --verbose $fgaddon_svn > $aircrafts
total=$(grep -c '<logentry' $aircrafts)
progress=0
echo parsing history
while xmlgetnext; do
case "$TAG" in
'logentry revision='*)
eval $(echo ${TAG#* })
for action in ${!revpath[@]}; do
unset revpath[$action]
done
;;
'author')
revauthor=${VALUE//\'/\'\'}
;;
'date')
revdate=$(date +%s -d "$VALUE")
;;
'path '*)
TAG=${TAG#* }
TAG=${TAG// /;}
TAG=${TAG//-/_}
eval $(echo ${TAG// /;})
path=(${VALUE//\// })
if test $kind = 'file' -a ${#path[@]} -gt 3; then
revpath[$action]+="$VALUE "
elif test $kind = 'dir' -a ${#path[@]} -eq 3 -a $action = 'D'; then
unset revindex[_${path[2]}] revauthor[_${path[2]}] revdate[_${path[2]}]
for sx in ${!setxmlmodified[@]}; do
[[ "$sx" =~ "${path[2]}/" ]] && unset setxmlmodified[$sx]
done
sqlite_request "delete from aircrafts where name = '${path[2]}'"
fi
;;
'/logentry')
for item in ${revpath[D]}; do
path=(${item//\// })
if [[ "${path[3]}" =~ "-set.xml" ]]; then
unset setxmlmodified[${path[2]}/${path[3]/-set.xml}]
sqlite_request "delete from setxml where file = '${path[3]/-set.xml}'"
fi
done
for action in A M R; do
for item in ${revpath[$action]}; do
path=(${item//\// })
test -z "${path[2]}" && continue # avoid empty
revindex[_${path[2]}]=$revision
revauthor[_${path[2]}]=$revauthor
revdate[_${path[2]}]=$revdate
[[ "${path[3]}" =~ "-set.xml" ]] && setxmlmodified[${path[2]}/${path[3]/-set.xml}]=1
done
done
newprogress=$((++logentry * 100 / $total))
if test $(( $newprogress - $progress )) -ge ${progress_granularity:-1}; then
progress=$newprogress
echo "$progress% (${#revindex[@]})"
fi
;;
'/log')
apply_revision
break
;;
esac
done < $aircrafts
########################################################################
# some aircrafts (mostly from the helijah's files architecture template)
# break because of infinite loop in middle of file
# I can't find the reason of this infinite loop
# these are the steps (some may be scripted)
# sorry about inconvenience...
# STEPS TO FOLLOW
# 1 - the following lines may be copied in a separate file (e.g /dev/shm/foo)
# 2 - uncomment by removing the FIRST column only and save the file
# 3 - uncomment the lines dedicated to save the setxml content in this script
# 4 - find the empty /sim/model/path:
# sqlite3 /your/database <<< 'select printf("%s/%s", aircrafts.name, setxml.file)
# from aircrafts inner join setxml
# where aircrafts.id = setxml.variantof and setxml.`/sim/model/path` = ""'
# 5 - play $ DB=/your/database ./fgaddon aicrafts_name/setxml_file
# 6 - play $ /dev/shm/foo aicrafts_name/setxml_file
# 7 - play $ sqlite3 /your/database <<< "update setxml set `/sim/model/path` = '<the path found>' where file = 'setxml_file'"
#
# exemple of one-line CLI:
# for i in $(sqlite3 .fgfs/flightgear-fgaddon/fgaddon.db <<< 'select printf("%s/%s", aircrafts.name, setxml.file) from aircrafts inner join setxml where aircrafts.id = setxml.variantof and setxml.`/sim/model/path` = ""'); do DB=.fgfs/flightgear-fgaddon/fgaddon.db .fgfs/fgaddon $i; sim_model_path=$(/dev/shm/foo $i | awk '/^\/sim\/model\/path/{print $NF}'); test -n "$sim_model_path" && sqlite3 .fgfs/flightgear-fgaddon/fgaddon.db <<< "update setxml set \`/sim/model/path\` = '$sim_model_path' where file = '${i#*/}'"; done
##!/bin/bash
#
#declare -A data=(
# [/sim/model/path]=text
#)
#data_pattern=$(printf "%s|" ${!data[@]})
#data_pattern=${data_pattern:0:-1}
#for col in ${!data[@]}; do
# data[$col]=
#done
#
#function xmlgetnext () {
# local IFS='>'
# read -d '<' TAG VALUE
# # by design, the first TAG/VALUE pair is empty
# # to avoid infinite loops at end of file parsing we return an error
# # the next time we find an empty TAG
# if test -z "$TAG"; then
# test ${xmlgetnext_empty_tag:-0} -gt 0 && return 1
# xmlgetnext_empty_tag=$(( xmlgetnext_empty_tag + 1 ))
# fi
# # process $TAG only if necessary
# local _TAG=$(printf '%q' $TAG)
# if test ${_TAG:0:1} = '$'; then
# TAG=$(tr '\n' ' ' <<< $TAG | sed 's/ */ /g; s/ *$//')
# fi
#}
#
#while xmlgetnext; do
# case "${TAG:0:1}" in
# ''|'?'|'!')
# continue;;
# /)
# property=${property%/*};;
# *)
# if test "${TAG: -1}" != '/'; then
# property+=/${TAG%% *}
# fi;;
# esac
#
# if [[ "$property" = /PropertyList@($data_pattern) ]]; then
# if test -z "${data[${property/\/PropertyList}]}"; then
# eval "data[${property/\/PropertyList}]=\"${VALUE//\"/\\\"}\""
# data[${property/\/PropertyList}]=$(tr '\n' ' ' <<< ${data[${property/\/PropertyList}]} | sed -r 's/^\s*//;s/\s+/ /g;s/\s*$//')
# echo "${property/\/PropertyList} : ${data[${property/\/PropertyList}]}"
# fi
# fi
#done < /dev/shm/aircrafts/$1