File indexing completed on 2024-04-28 15:40:17

0001 #!/bin/bash
0002 # SPDX-FileCopyrightText: 2012-2018 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 #
0004 # SPDX-License-Identifier: BSD-2-Clause
0005 
0006 QTPATHS=qtpaths
0007 if ! $QTPATHS --types >/dev/null
0008 then
0009         echo "QStandardPaths command line client ($QTPATHS) not usable!" >&2
0010         exit 1
0011 fi
0012 
0013 # default locations:
0014 KPARC=`$QTPATHS --locate-file ConfigLocation kphotoalbumrc`
0015 KPAUIRC=`$QTPATHS --locate-file GenericDataLocation kxmlgui5/kphotoalbum/kphotoalbumui.rc`
0016 if [ -z "$KPAUIRC" ]
0017 then
0018         echo "Info: using old location for ui.rc files..." >&2
0019         KPAUIRC=`$QTPATHS --locate-file GenericDataLocation kphotoalbum/kphotoalbumui.rc`
0020 fi
0021 BACKUP_LOCATION=~/kpa-backup
0022 BACKUP_ID=latest
0023 ACTION=
0024 ADD_FILES_RELATIVE="exif-info.db layout.dat"
0025 KEEP_NUM=5
0026 TERSE=
0027 NO_ACT=
0028 
0029 SQLITE=sqlite3
0030 
0031 ###
0032 # Helper functions:
0033 ###
0034 
0035 get_config_value()
0036 {
0037         sed -n 's/#.*// ; s/'$1'=\(.*\)/\1/p' "$KPARC"
0038 }
0039 
0040 resolve_link()
0041 # Use readlink to resolve the given filename.
0042 # If the file is no symlink, just return the filename.
0043 {
0044         if [ -L "$1" ]
0045         then
0046                 readlink "$1"
0047         else
0048                 echo "$1"
0049         fi
0050 }
0051 
0052 print_help()
0053 {
0054         echo "Usage: $0 -b|--backup OPTIONS..." >&2
0055         echo "       $0 -r|--restore OPTIONS..." >&2
0056         echo "       $0 -l|--list [--terse] OPTIONS..." >&2
0057         echo "       $0 -i|--info OPTIONS..." >&2
0058         echo "       $0 -p|--purge [--keep NUM] OPTIONS..." >&2
0059         echo "" >&2
0060         echo "Create or restore a backup of your essential KPhotoAlbum files." >&2
0061         echo "Note: your actual image-files are not backed up!" >&2
0062         echo "" >&2
0063         echo "Options:" >&2
0064         echo "-d|--directory BACKUP_LOCATION   Use the specified path as backup location" >&2
0065         echo "                                 [default: $BACKUP_LOCATION]" >&2
0066         echo "--id BACKUP_ID                   Use given backup instead of latest.">&2
0067         echo "-n|--no-act                      Do not take any action." >&2
0068         echo "" >&2
0069         echo "List options:" >&2
0070         echo "--terse                          Only show backup ids, no change information." >&2
0071         echo "" >&2
0072         echo "Purge options:" >&2
0073         echo "--keep NUM                       Keep the latest NUM backups." >&2
0074         echo "                                 [default: $KEEP_NUM]" >&2
0075         echo "" >&2
0076         echo "Actions:" >&2
0077         echo "-b|--backup" >&2
0078         echo "                                 Create a new backup." >&2
0079         echo "-r|--restore" >&2
0080         echo "                                 Restore the latest backup (or the one given by --id)." >&2
0081         echo "-l|--list" >&2
0082         echo "                                 List all backups, in the same format as --info." >&2
0083         echo "                                 If --terse is given: show a list of all backup ids." >&2
0084         echo "-i|--info" >&2
0085         echo "                                 Show which files in the latest backup (or the one specified by --id)" >&2
0086         echo "                                 have changed compared to the current state." >&2
0087         echo "-p|--purge" >&2
0088         echo "                                 Delete all but the latest $KEEP_NUM backups." >&2
0089         echo "" >&2
0090 }
0091 
0092 sqlite_diff()
0093 # sqlite_diff SRC DST QUERYTEXT
0094 # visual comparison for exif-info.db
0095 # Either SRC or DST may be "-" to denote input from stdin.
0096 # QUERYTEXT should be a descriptive query for the db, but even it does
0097 # not have to include every aspect of the data.
0098 # After the data-diff on the query, a diff on the whole file will determine
0099 # the status of sqlite_diff.
0100 {
0101         local src="$1"
0102         local dst="$2"
0103         local query="$3"
0104         # catch "exif_diff - -":
0105         if [ "$src" = "$dst" ]
0106         then
0107                 echo "Identical files!" >&2
0108                 return 0
0109         fi
0110 
0111         if ! "$SQLITE" -version >/dev/null
0112         then
0113                 echo "Warning: sqlite not found." >&2
0114                 diff -q -s "$1" "$2"
0115         else
0116                 local tmp=`mktemp`
0117                 local srctmp=`mktemp`
0118                 local dsttmp=`mktemp`
0119                 if [ "$src" = "-" ]
0120                 then
0121                         src="$tmp"
0122                         cat > "$tmp"
0123                 fi
0124                 if [ "$dst" = "-" ]
0125                 then
0126                         dst="$tmp"
0127                         cat > "$tmp"
0128                 fi
0129 
0130                 "$SQLITE" "$src" "$query" > "$srctmp"
0131                 "$SQLITE" "$dst" "$query" > "$dsttmp"
0132 
0133                 # display unified diff of query result:
0134                 diff -u --label "$1" --label "$2" "$srctmp" "$dsttmp"
0135                 # diff the actual file:
0136                 diff -q --label "$1" --label "$2" "$src" "$dst"
0137                 local retval=$?
0138 
0139                 # cleanup
0140                 rm "$tmp" "$srctmp" "$dsttmp"
0141                 return $retval
0142         fi
0143 }
0144 
0145 exif_diff()
0146 # exif_diff SRC DST
0147 {
0148         sqlite_diff "$1" "$2" "select * from exif"
0149 }
0150 
0151 visual_diff()
0152 # visual_diff [-q] SRC DST
0153 # Do a diff of two files (or STDIN and a file).
0154 # An appropriate diff format is used, based on the given file.
0155 # If -q is given, no output is given.
0156 # The exit value is just like from GNU diff.
0157 {
0158         local quiet
0159         local src
0160         local dst
0161         local filename
0162         for param
0163         do
0164                 case "$param" in
0165                         -q) #quiet,question
0166                                 quiet=1
0167                                 ;;
0168                         *)
0169                                 if [ -z "$src" ]
0170                                 then
0171                                         src="$param"
0172                                 else if [ -z "$dst" ]
0173                                         then
0174                                                 dst="$param"
0175                                         else
0176                                                 echo "visual_diff: incorrect number of parameters!" >&2
0177                                                 exit 1
0178                                         fi
0179                                 fi
0180                                 # we need a valid filename to determine which specialized diff we should use
0181                                 # usually, one parameter is "-", so we take the param that's a real filename
0182                                 [ -f "$param" ] && filename="$param"
0183                                 ;;
0184                 esac
0185         done
0186 
0187         if [ -n "$quiet" ]
0188         then
0189                 # just do the quickest thing:
0190                 diff -q "$src" "$dst" >/dev/null
0191         else
0192                 filename=`basename "$filename"`
0193                 case "$filename" in
0194                         exif-info.db)
0195                                 exif_diff "$src" "$dst"
0196                                 ;;
0197                         index.xml)
0198                                 diff -u -F '^    <.*' "$src" "$dst"
0199                                 ;;
0200                         kphotoalbumui.rc)
0201                                 diff -u --ignore-space-change -F '^\ *<\(Menu\|ToolBar\).*' "$src" "$dst"
0202                                 ;;
0203                         kphotoalbumrc)
0204                                 diff -u -F '^\[.*\]' "$src" "$dst"
0205                                 ;;
0206                         *)
0207                                 # data; nothing to see:
0208                                 diff -q "$src" "$dst" >/dev/null
0209                                 ;;
0210                 esac
0211         fi
0212 }
0213 
0214 untar_if_changed()
0215 # untar the given single-file-tarball if the destination file has changed
0216 # use first parameter -p to print when the file has changed, but not untar it.
0217 {
0218         local printonly=false
0219         [ -n "$NO_ACT" ] && printonly=true
0220         local quiet
0221         local tarfile
0222         for param
0223         do
0224                 case "$param" in
0225                         -p)
0226                                 printonly=true
0227                                 ;;
0228                         -q) #quiet
0229                                 quiet=-q
0230                                 ;;
0231                         *)
0232                                 tarfile="$param"
0233                                 ;;
0234                 esac
0235         done
0236         [ -f "$tarfile" ] || return 1
0237         local dstfile=`tar -Ptz -f "$tarfile"`
0238         if tar -PxzO -f "$tarfile" | visual_diff $quiet - "$dstfile"
0239         then
0240                 echo "unchanged: $dstfile"
0241         else
0242                 if $printonly
0243                 then
0244                         echo "  changed: $dstfile"
0245                 else
0246                         tar -Pwxvz -f "$tarfile"
0247                 fi
0248         fi
0249 }
0250 
0251 do_backup()
0252 {
0253         local INDEXFILE=
0254         local KPA_FOLDER=
0255         ###
0256         # Query file locations from RC files & check parameter sanity:
0257         ###
0258 
0259         if [ ! -r "$KPARC" ]
0260         then
0261                 echo "RC-file ($KPARC) not readable!" >&2
0262                 exit 1
0263         fi
0264         # KPA gets the image directory from the imageDBFile entry
0265         INDEXFILE=`get_config_value imageDBFile`
0266         if [ -z "$INDEXFILE" ]
0267         then
0268                 echo "The RC-file ($KPARC) does not define an entry for index.xml!" >&2
0269                 exit 1
0270         fi
0271         if [ ! -f "$INDEXFILE" ]
0272         then
0273                 echo "KPhotoAlbum index file does not exist!" >&2
0274                 exit 1
0275         fi
0276         KPA_FOLDER=`dirname "$INDEXFILE"`
0277         if [ ! -d "$KPA_FOLDER" ]
0278         then
0279                 echo "KPhotoAlbum image directory ($KPA_FOLDER) does not exist!" >&2
0280                 exit 1
0281         fi
0282 
0283         if [ ! -d "$BACKUP_LOCATION" ]
0284         then
0285                 echo "Backup location ($BACKUP_LOCATION) is not a directory, creating it." >&2
0286                 [ -z "$NO_ACT" ] && mkdir "$BACKUP_LOCATION" || exit 1
0287         fi
0288         BACKUP_LOCATION_WDATE="$BACKUP_LOCATION"/"`date +%Y%m%d-%H%M%S`"
0289         echo "Writing backup to $BACKUP_LOCATION_WDATE"
0290         [ -z "$NO_ACT" ] && mkdir "$BACKUP_LOCATION_WDATE"
0291         if [ -e "$BACKUP_LOCATION"/latest ]
0292         then
0293                 [ -z "$NO_ACT" ] && rm "$BACKUP_LOCATION"/latest
0294         fi
0295         [ -z "$NO_ACT" ] && ln -s "$BACKUP_LOCATION_WDATE" "$BACKUP_LOCATION"/latest
0296 
0297         echo "Backing up essential files..."
0298         if [ -z "$NO_ACT" ]
0299         then
0300                 for f in "$KPARC" "$INDEXFILE"
0301                 do
0302                         tar -Pcvz -f "$BACKUP_LOCATION_WDATE"/`basename "$f"`.tgz "$f"
0303                 done
0304         fi
0305         echo "Backing up additional files..."
0306         if [ -z "$NO_ACT" ]
0307         then
0308                 [ -f "$KPAUIRC" ] && tar -Pcvz -f "$BACKUP_LOCATION_WDATE"/`basename "$KPAUIRC"`.tgz "$KPAUIRC"
0309                 for f in $ADD_FILES_RELATIVE
0310                 do
0311                         [ -f "$KPA_FOLDER/$f" ] && tar -Pcvz -f "$BACKUP_LOCATION_WDATE"/`basename "$f"`.tgz "$KPA_FOLDER/$f"
0312                 done
0313         fi
0314 }
0315 
0316 do_restore()
0317 {
0318         if [ ! -d "$BACKUP_LOCATION" ]
0319         then
0320                 echo "Backup location ($BACKUP_LOCATION) is not a directory!" >&2
0321                 exit 1
0322         fi
0323         echo "Restoring essential files..."
0324         for f in "$BACKUP_LOCATION/$BACKUP_ID"/*.tgz
0325         do
0326                 # untar_if_changed honors NO_ACT:
0327                 untar_if_changed "$f"
0328         done
0329 }
0330 
0331 show_info()
0332 # show_info BACKUP_DIR [ANNOTATION]
0333 # shows if a given backup location has changes to the current state
0334 # if ANNOTATON is given, is is written next to the backup time
0335 {
0336         backup_dir="$1"
0337         backup_name=`basename "$1"`
0338         annotation="$2"
0339         echo -n " -$backup_name" | sed 's/\(....\)\(..\)\(..\)-\(..\)\(..\)\(..\)/\0 [\1-\2-\3 \4:\5:\6]/'
0340         if [ -n "$annotation" ]
0341         then
0342                 echo -n " $annotation"
0343         fi
0344         echo
0345         for f in "$backup_dir"/*.tgz
0346         do
0347                 echo -n "  |-"
0348                 untar_if_changed -p -q "$f"
0349         done
0350         echo
0351 }
0352 
0353 do_list()
0354 {
0355         if [ ! -d "$BACKUP_LOCATION" ]
0356         then
0357                 echo "Backup location ($BACKUP_LOCATION) is not a directory!" >&2
0358                 exit 1
0359         fi
0360         local LATEST=`resolve_link "$BACKUP_LOCATION/latest"`
0361         LATEST=`basename "$LATEST"`
0362         local action=show_info
0363         [ -n "$TERSE" ] && action=basename
0364         echo "$BACKUP_LOCATION:"
0365         for d in "$BACKUP_LOCATION"/*
0366         do
0367                 if [ -d "$d" ]
0368                 then
0369                         [ -L "$d" ] && continue
0370                         if [ "`basename "$d"`" = "$LATEST" ]
0371                         then
0372                                 $action "$d" "(*latest*)"
0373                         else
0374                                 $action "$d"
0375                         fi
0376                 fi
0377         done
0378 }
0379 
0380 do_info()
0381 {
0382         if [ ! -d "$BACKUP_LOCATION" ]
0383         then
0384                 echo "Backup location ($BACKUP_LOCATION) is not a directory!" >&2
0385                 exit 1
0386         fi
0387         local LATEST=`resolve_link "$BACKUP_LOCATION/$BACKUP_ID"`
0388         echo "$BACKUP_LOCATION:"
0389         show_info "$LATEST"
0390 }
0391 
0392 do_purge()
0393 {
0394         if [ ! -d "$BACKUP_LOCATION" ]
0395         then
0396                 echo "Backup location ($BACKUP_LOCATION) is not a directory!" >&2
0397                 exit 1
0398         fi
0399         cd "$BACKUP_LOCATION"
0400         # list newest entries first, skip KEEP_NUM directories:
0401         for d in `ls -t1`
0402         do
0403                 if [ -h "$d" -o ! -d "$d" ]
0404                 then # skip "latest"
0405                         echo "Skipping non-directory $d"
0406                         continue
0407                 fi
0408                 if [ "$KEEP_NUM" -gt 0 ]
0409                 then
0410                         echo "Skipping $d..."
0411                         let KEEP_NUM--
0412                         continue
0413                 fi
0414                 echo "Purging backup $d..."
0415                 [ -z "$NO_ACT" ] && rm -rf "$d"
0416         done
0417 }
0418 
0419 ###
0420 # Parse commandline:
0421 ###
0422 
0423 TEMP=`getopt -o hbrlipnd: --long help,backup,restore,list,info,purge,no-act,directory:,keep:,id:,terse \
0424      -n 'kpa-backup' -- "$@"`
0425 
0426 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
0427 
0428 # Note the quotes around `$TEMP': they are essential!
0429 eval set -- "$TEMP"
0430 
0431 while true ; do
0432         case "$1" in
0433                 -h|--help) print_help ; exit ;;
0434                 -b|--backup) ACTION=do_backup ; shift ;;
0435                 -r|--restore) ACTION=do_restore ; shift ;;
0436                 -l|--list) ACTION=do_list ; shift ;;
0437                 -i|--info) ACTION=do_info ; shift ;;
0438                 -p|--purge) ACTION=do_purge ; shift ;;
0439                 -n|--no-act) NO_ACT=1 ; shift ;;
0440                 -d|--directory) BACKUP_LOCATION="$2" ; shift 2 ;;
0441                 --keep) KEEP_NUM="$2" ; shift 2 ;;
0442                 --id) BACKUP_ID="$2" ; shift 2 ;;
0443                 --terse) TERSE=1 ; shift ;;
0444                 --) shift ; break ;;
0445                 *) echo "Internal error!" ; exit 1 ;;
0446         esac
0447 done
0448 
0449 if [ "$#" -gt 0 ]
0450 then
0451         echo "Unknown extra parameters: $@" >&2
0452         exit 1
0453 fi
0454 
0455 ###
0456 # Perform action:
0457 ###
0458 
0459 if [ -z "$ACTION" ]
0460 then
0461         echo "No action chosen!" >&2
0462         print_help
0463         exit 1
0464 fi
0465 
0466 "$ACTION"