File indexing completed on 2024-05-05 04:22:12
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"