#!/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_url=https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Aircraft 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_firstentry:-1} -eq 1 && xmlgetnext_firstentry=0 || return 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 } function 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 } 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 missing_setxml=$(sqlite_request "select name from aircrafts where id not in (select variantof from setxml)") if test -n "$missing_setxml"; then echo "missing setxml config: $missing_setxml" 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 () { set +x 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 ${!revision[@]}; do sqlite_request "insert into recover_rev values ( '$revkey', ${revision[$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 set +x exit } function update_database () { echo "[ ${#revision[@]} ] ${ac:1}" 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}', ${revision[$ac]}, ${revdate[$ac]}, '${revauthor[$ac]}')" elif test $dbupdate -lt ${revision[$ac]}; then sqlite_request "update aircrafts set revision = ${revision[$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}'") 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 export --quiet --force $fgaddon_svn/${ac:1}/$sx-set.xml $setxml; then register_state fi xmlremovecomments unset xmlgetnext_firstentry property while xmlgetnext; do case "${TAG:0:1}" in ''|'?'|'!') continue;; /) property=${property%/*};; *) if test "${TAG: -1}" != '/'; then property+=/${TAG%% *} fi;; # property+=/${TAG%% *} # if test "${TAG: -1}" = '/'; then # _TAG=${TAG:0:-1} # test "${_TAG#* }" != "${_TAG}" && eval "${_TAG#* }" # property=${property%/*} # 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 eval "data[${property/\/PropertyList}]=\"${VALUE//\"/\\\"}\"" data[${property/\/PropertyList}]=$(tr '\n' ' ' <<< ${data[${property/\/PropertyList}]} | sed -r 's/^\s*//;s/\s+/ /g;s/\s*$//') fi if test -n "$_TAG"; then # _TAG non-null means xml props ends with / # need to go back of last property property=${property/%\/${_TAG%% *}} unset _TAG 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 unset revision[$ac] } function apply_revision () { for ac in "${!revision[@]}"; 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 revision revauthor revdate setxmlmodified files 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('revision[%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") # for debugging purpose if test -n "$2"; then ac=_${1%/*} revision[$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('revision[_%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 "${revision[$ac]}"; then echo "aircraft ${ac:1} not found" rm $in_ram_database exit fi update_database 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 '