feat(user/bkots):Execute via array elements, not string concat
[BK-2020-03.git] / user / bkots
CommitLineData
b64bbf81
SBS
1#!/usr/bin/env bash
2
3# Define variables
4declare -Ag appRollCall # Associative array for storing app status
5declare -Ag fileRollCall # Associative array for storing file status
6declare -Ag dirRollCall # Associative array for storing dir status
7declare -ag arrayPosArgs # Associative array for processArgs() function
3a8398d2
SBS
8declare -ag calendars;
9calendars+=("https://finney.calendar.eternitywall.com");
10calendars+=("https://btc.calendar.catallaxy.com");
11calendars+=("https://alice.btc.calendar.opentimestamps.org");
12calendars+=("https://bob.btc.calendar.opentimestamps.org");
3434f509 13age_threshold="60"; # min age to add file; seconds;
5ed29928 14max_job_count="2"; # default max job count
b64bbf81
SBS
15
16# Declare functions
17yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
18die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
5ed29928 19must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
b64bbf81
SBS
20checkapp() {
21 # Desc: If arg is a command, save result in assoc array 'appRollCall'
22 # Usage: checkapp arg1 arg2 arg3 ...
23 # Version: 0.1.1
24 # Input: global assoc. array 'appRollCall'
25 # Output: adds/updates key(value) to global assoc array 'appRollCall'
26 # Depends: bash 5.0.3
27 local returnState
28
29 #===Process Args===
30 for arg in "$@"; do
31 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
32 appRollCall[$arg]="true";
33 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
34 else
35 appRollCall[$arg]="false"; returnState="false";
36 fi;
37 done;
38
39 #===Determine function return code===
40 if [ "$returnState" = "true" ]; then
41 return 0;
42 else
43 return 1;
44 fi;
45} # Check that app exists
46checkfile() {
47 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
48 # Usage: checkfile arg1 arg2 arg3 ...
49 # Version: 0.1.1
50 # Input: global assoc. array 'fileRollCall'
51 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
52 # Output: returns 0 if app found, 1 otherwise
53 # Depends: bash 5.0.3
54 local returnState
55
56 #===Process Args===
57 for arg in "$@"; do
58 if [ -f "$arg" ]; then
59 fileRollCall["$arg"]="true";
60 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
61 else
62 fileRollCall["$arg"]="false"; returnState="false";
63 fi;
64 done;
65
66 #===Determine function return code===
67 if [ "$returnState" = "true" ]; then
68 return 0;
69 else
70 return 1;
71 fi;
72} # Check that file exists
73checkdir() {
74 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
75 # Usage: checkdir arg1 arg2 arg3 ...
76 # Version 0.1.2
77 # Input: global assoc. array 'dirRollCall'
78 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
79 # Output: returns 0 if all args are dirs; 1 otherwise
80 # Depends: Bash 5.0.3
81 local returnState
82
83 #===Process Args===
84 for arg in "$@"; do
85 if [ -z "$arg" ]; then
86 dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
87 elif [ -d "$arg" ]; then
88 dirRollCall["$arg"]="true";
89 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
90 else
91 dirRollCall["$arg"]="false"; returnState="false";
92 fi
93 done
94
95 #===Determine function return code===
96 if [ "$returnState" = "true" ]; then
97 return 0;
98 else
99 return 1;
100 fi
101} # Check that dir exists
102displayMissing() {
103 # Desc: Displays missing apps, files, and dirs
104 # Usage: displayMissing
105 # Version 1.0.0
106 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
107 # Output: stderr: messages indicating missing apps, file, or dirs
108 # Output: returns exit code 0 if nothing missing; 1 otherwise
109 # Depends: bash 5, checkAppFileDir()
110 local missingApps value appMissing missingFiles fileMissing
111 local missingDirs dirMissing
112
113 #==BEGIN Display errors==
114 #===BEGIN Display Missing Apps===
115 missingApps="Missing apps :";
116 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
117 for key in "${!appRollCall[@]}"; do
118 value="${appRollCall[$key]}";
119 if [ "$value" = "false" ]; then
120 #echo "DEBUG:Missing apps: $key => $value";
121 missingApps="$missingApps""$key ";
122 appMissing="true";
123 fi;
124 done;
125 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
126 echo "$missingApps" 1>&2;
127 fi;
128 unset value;
129 #===END Display Missing Apps===
130
131 #===BEGIN Display Missing Files===
132 missingFiles="Missing files:";
133 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
134 for key in "${!fileRollCall[@]}"; do
135 value="${fileRollCall[$key]}";
136 if [ "$value" = "false" ]; then
137 #echo "DEBUG:Missing files: $key => $value";
138 missingFiles="$missingFiles""$key ";
139 fileMissing="true";
140 fi;
141 done;
142 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
143 echo "$missingFiles" 1>&2;
144 fi;
145 unset value;
146 #===END Display Missing Files===
147
148 #===BEGIN Display Missing Directories===
149 missingDirs="Missing dirs:";
150 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
151 for key in "${!dirRollCall[@]}"; do
152 value="${dirRollCall[$key]}";
153 if [ "$value" = "false" ]; then
154 #echo "DEBUG:Missing dirs: $key => $value";
155 missingDirs="$missingDirs""$key ";
156 dirMissing="true";
157 fi;
158 done;
159 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
160 echo "$missingDirs" 1>&2;
161 fi;
162 unset value;
163 #===END Display Missing Directories===
164
165 #==END Display errors==
166 #==BEGIN Determine function return code===
167 if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then
168 return 1;
169 else
170 return 0;
171 fi
172 #==END Determine function return code===
173} # Display missing apps, files, dirs
174vbm() {
175 # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
176 # Usage: vbm "DEBUG :verbose message here"
177 # Version 0.2.0
178 # Input: arg1: string
179 # vars: opVerbose
180 # Output: stderr
181 # Depends: bash 5.0.3, GNU-coreutils 8.30 (echo, date)
182
183 if [ "$opVerbose" = "true" ]; then
184 functionTime="$(date --iso-8601=ns)"; # Save current time in nano seconds.
185 echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
186 fi
187
188 # End function
189 return 0; # Function finished.
190} # Displays message if opVerbose true
191showVersion() {
192 # Desc: Displays script version and license information.
193 # Usage: showVersion
194 # Version: 0.0.1
195 # Input: scriptVersion var containing version string
196 # Output: stdout
197 # Depends: vbm(), yell, GNU-coreutils 8.30
198
199 # Initialize function
200 vbm "DEBUG:showVersion function called."
201
202 cat <<'EOF'
5ed29928 203bkots 2.0.0
b64bbf81
SBS
204Copyright (C) 2022 Steven Baltakatei Sandoval
205License GPLv3: GNU GPL version 3
206This is free software; you are free to change and redistribute it.
207There is NO WARRANTY, to the extent permitted by law.
208
209 GNU Coreutils 8.32
210 Copyright (C) 2020 Free Software Foundation, Inc.
211 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
212 This is free software: you are free to change and redistribute it.
213 There is NO WARRANTY, to the extent permitted by law.
214EOF
215
216 # End function
217 vbm "DEBUG:showVersion function ended."
218 return 0; # Function finished.
219} # Display script version.
220showUsage() {
221 # Desc: Display script usage information
222 # Usage: showUsage
223 # Version 0.0.1
224 # Input: none
225 # Output: stdout
226 # Depends: GNU-coreutils 8.30 (cat)
227 cat <<'EOF'
228 USAGE:
229 bkots [ options ] [PATH...]
230
231 POSITIONAL ARGUMENTS:
232 PATH Path(s) of file(s) or directory(ies)
233
234 OPTIONS:
235 --dry-run
236 Do everything except run 'ots' commands.
237 -h, --help
238 Display help information.
239 --include-dotfiles
240 Include files and directories starting with '.' (not
241 included by default).
5ed29928
SBS
242 -j, --jobs
243 Specify simultaneous job count (default: 2)
b64bbf81
SBS
244 -r, --recursive
245 Consider files in dirs recursively.
246 --version
247 Display script version.
248 -v, --verbose
249 Display debugging info.
250 --
251 Mark end of options. Interpret remaining arguments as
252 positional arguments.
253
254 DESCRIPTION:
255 Scans files by file paths or directory paths provided by
256 positional arguments to see if Open Timestamps '.ots' file
257 exists. If so, attempt to upgrade and verify the '.ots'
258 file. If no '.ots' file exists, attempt to create one.
259
260 Files with a dotfile parent directory located anywhere in the
261 file path are ignored by default. (e.g. 'HEAD' in
262 '/home/user/diary/.git/logs/HEAD' because of '.git'). Dotfiles
263 themselves are also ignored by default
264 (e.g. '/home/user/.gitconfig').
265
3434f509
SBS
266 Files modified less than 1 minute ago are ignored.
267
b64bbf81
SBS
268 EXAMPLES:
269 bkots -v foo.txt
270 bkots foo.txt bar.pdf /home/username/Pictures/
271EOF
272} # Display information on how to use this script.
5ed29928
SBS
273count_jobs() {
274 # Desc: Count and return total number of jobs
275 # Usage: count_jobs
276 # Input: None.
277 # Output: stdout integer number of jobs
278 # Depends: Bash 5.1.16
279 # Example: while [[$(count_jobs) -gt 0]]; do echo "Working..."; sleep 1; done;
280 # Version: 0.0.1
281
282 local job_count;
283 job_count="$(jobs -r | wc -l | tr -d ' ' )";
284 #yell "DEBUG:job_count:$job_count";
285 if [[ -z $job_count ]]; then job_count="0"; fi;
286 echo "$job_count";
287}; # Return number of background jobs
288wait_for_jobslot() {
289 # Desc: Does not return until count_jobs() falls below $max_job_count
290 # Input: var max_job_count
291 # Output: return code 0
292 # Depends: count_jobs(), yell();
293 while [[ $(count_jobs) -ge $max_job_count ]]; do
294 printf "\r%d:Working..." "$SECONDS";
295 sleep 1;
296 done;
297 return 0;
298};
b64bbf81
SBS
299processArgs() {
300 # Desc: Processes arguments provided to script.
301 # Usage: processArgs "$@"
302 # Version: 1.0.0
303 # Input: "$@" (list of arguments provided to the function)
304 # Output: Sets following variables used by other functions:
305 # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
306 # arrayPosArgs Array of remaining positional argments
307 # Depends:
308 # yell() Displays messages to stderr.
309 # vbm() Displays messsages to stderr if opVerbose set to "true".
310 # showUsage() Displays usage information about parent script.
311 # showVersion() Displays version about parent script.
312 # arrayPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option).
313 # External dependencies: bash (5.1.16), echo
314 # Ref./Attrib.:
315 # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347
316 # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams
317
318 # Initialize function
319 vbm "DEBUG:processArgs function called."
320
321 # Perform work
322 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
323 #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1].
324 #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1].
325 case "$1" in
326 --dry-run) # Do not run ots commands
327 option_dry_run="true";
328 vbm "DEBUG:Option enabled:dry run";;
329 -h | --help) showUsage; exit 1;; # Display usage.
330 --include-dotfiles) # Include dotfiles
331 option_include_dotfiles="true";
332 vbm "DEBUG:Option enabled:include dotfiles";;
5ed29928
SBS
333 -j | --jobs) # Specify max_job_count
334 if [[ -n "$2" ]] && [[ "$2" =~ ^[0-9]+$ ]]; then
335 max_job_count="$2";
336 vbm "STATUS:Max job count set to:$max_job_count";
337 shift;
338 else
339 showUsage;
340 die "FATAL:Invalid job count:$2";
341 fi;;
b64bbf81
SBS
342 -r | --recursive) # Specify recursive option
343 option_recursive="true";
344 vbm "DEBUG:option enabled:include files in dirs recursively";;
345 --version) showVersion; exit 1;; # Show version
346 -v | --verbose) opVerbose="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
347 --) # End of all options. See [2].
348 shift;
349 for arg in "$@"; do
350 vbm "DEBUG:adding to arrayPosArgs:$arg";
351 arrayPosArgs+=("$arg");
352 done;
353 break;;
354 -*) showUsage; yell "ERROR: Unrecognized option."; exit 1;; # Display usage
355 *) # Assume remaining arguments are positional arguments
356 for arg in "$@"; do
357 vbm "DEBUG:adding to arrayPosArgs:$arg";
358 arrayPosArgs+=("$arg");
359 done;
360 break;;
361 #*) showUsage; yell "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options. See [1].
362 esac
363 shift
364 done
365
366 # End function
367 vbm "DEBUG:processArgs function ended."
368 return 0; # Function finished.
369}; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
370get_parent_dirnames() {
371 # Desc: Provides newline-delimited list of each parent dir of a file or dir
372 # Usage: get_parent_dirnames arg1
373 # Input: arg1 input path
374 # Output: stdout newline-delimited list of parent dirs
375 # Version: 0.0.1
5ed29928 376 # Depends: yell(), die(), must()
b64bbf81
SBS
377 local path
378
379 # Check input
380 if [[ $# -ne 1 ]]; then die "FATAL:Incorrect number of arguments:$#"; fi;
381 if ! { [[ -f $1 ]] || [[ -d $1 ]]; }; then die "FATAL:Not a file or dir:$1"; fi;
382
383 # Process path
384 path="$1";
385 while [[ -f $path ]] || [[ -d $path ]]; do
386 path="$(dirname "$path")";
387 name_base_previous="$name_base";
388 name_base="$(basename "$path")";
389 ## Check for stop condition (dirname returns same result as previous iteration)
390 if [[ $name_base == "$name_base_previous" ]]; then break; fi;
391 echo "$name_base";
392 done;
393}; # Output parent dirnames to stdout
394main() {
395 # Desc: Creates `.ots` file:
396 # - for each file specified in arrayPosArgs array
397 # - for each file in each dir specified in arrayPosArgs array
398 # Output file created alongside each file or in output directory specified by pathDirIn1
399 # Usage: main "$@";
3434f509
SBS
400 # Input: arrayPosArgs array with positional arguments
401 # pathDirOut1 path for output `.ots` files (if pathDirOut1 is specified and is a path)
402 # age_threshold var: mininum age in seconds to timestamp file
403
b64bbf81 404 # Output: file(s) creates `.ots` file alongside specified files
90152692 405 # Depends: find (GNU findutils) 4.8.0, GNU Coreutils 8.32 (sort), GNU Parallel 20210822
b64bbf81
SBS
406 # Ref/Attrib: [1] How to create an array of unique elements from a string/array in bash https://unix.stackexchange.com/a/167194
407 # [2] How to find files containing newlines in their names https://stackoverflow.com/a/21727028
3434f509 408 # [3] Get mtime of specific file using Bash? https://stackoverflow.com/a/4774377
90152692 409 # [4] Search/Replace with string substitution instead of sed. https://www.shellcheck.net/wiki/SC2001
b64bbf81
SBS
410 local -a file_list file_list_pruned;
411 local -a files_to_verify files_to_upgrade files_to_stamp
3a8398d2
SBS
412 local -a files_to_verify_pruned files_to_upgrade_pruned files_to_stamp_pruned
413
b64bbf81
SBS
414 # Process args
415 processArgs "$@";
416
417 # Check dependencies
90152692 418 if ! checkapp ots find parallel; then
b64bbf81
SBS
419 displayMissing;
420 die "FATAL:Missing dependencies.";
421 fi;
422
3434f509
SBS
423 # Check arguments
424 for arg in "${arrayPosArgs[@]}"; do
425 arg="$(readlink -f "$arg")";
426 if ! { [[ -d $arg ]] || [[ -f $arg ]]; }; then
427 die "FATAL:Not a file or dir:arg:$arg";
b64bbf81 428 fi;
3434f509 429 done;
b64bbf81
SBS
430
431 # Display ots details
432 vbm "$(type ots)"; # show how 'ots' is defined
433 #TODO: add option to define 'ots' as a bash function that
434 #populates the ots option '--bitcoin-node FILE' with a
435 #user-specified FILE.
436
437 # Populate file_list
438 vbm "DEBUG:begin populate file_list array";
439 for item in "${arrayPosArgs[@]}"; do
440 vbm "DEBUG:adding to file list:item:$item";
441
442 ## Get full canonicalized path (follow symlinks)
443 item="$(readlink -f "$item")";
444 vbm "DEBUG:item full path:item:$item";
445
446 ## Add to list: files
447 if [[ -f $item ]]; then
448 vbm "DEBUG:is a file:item:$item";
449 file_list+=("$item");
450 vbm "DEBUG:added to file_list:$item";
451 ## Add to list: files in dirs
452 elif [[ -d $item ]]; then
453 vbm "DEBUG:is a dir:item:$item";
454 ### Check for recursive flag
455 if [[ "$option_recursive" == "true" ]]; then
456 vbm "DEBUG:option_recursive:$option_recursive";
457 while read -r line; do
458 file_list+=("$line");
459 vbm "DEBUG:added to file_list:$line";
460 done < <(find "$item" -type f);
461 else
462 while read -r line; do
463 file_list+=("$line");
464 vbm "DEBUG:added to file_list:$line";
465 done < <(find "$item" -maxdepth 1 -type f);
466 fi;
467 else
3434f509 468 die "FATAL:Not a file or dir:item:$item";
b64bbf81
SBS
469 fi;
470 done;
471 if [[ $opVerbose == "true" ]]; then
472 vbm "DEBUG:file_list:";
473 printf "%s\n" "${file_list[@]}";
474 fi;
475
476 # Prune file_list
477 for item in "${file_list[@]}"; do
3434f509
SBS
478 ## Ignore files that end in '.ots.bak'.
479 if [[ $item =~ '.ots.bak'$ ]]; then
90152692 480 vbm "DEBUG:Skipping file ending in '.ots.bak':item:$item";
3434f509
SBS
481 continue; # skip to next item
482 fi;
483
484 ## Ignore dotfiles
b64bbf81 485 if ! [[ $option_include_dotfiles == "true" ]]; then
90152692
SBS
486 ### Ignore files if '/.' contained within canonical path
487 pattern="/\."; # a dot after a forward slash
488 if [[ $item =~ $pattern ]]; then
489 continue;
b64bbf81 490 fi;
90152692
SBS
491
492 # ### Ignore files located beneath a dotfile directory (e.g. '/home/my_repo/.git/config')
493 # unset flag_contains_dotfile_parent;
494 # while read -r line; do
495 # #### Check line from output of get_parent_dirnames
496 # pattern="^\.";
497 # if [[ $line =~ $pattern ]]; then
498 # ##### line starts with '.'
499 # vbm "DEBUG:Dotfile parent detected. Not including in file_list_pruned:$item";
500 # vbm "DEBUG:Dotfile in path:item:$item";
501 # vbm "DEBUG:Dotfile parent:line:$line";
502 # flag_contains_dotfile_parent="true";
503 # break
504 # fi;
505 # done < <(get_parent_dirnames "$item");
506 # if [[ $flag_contains_dotfile_parent == "true" ]]; then
507 # unset flag_contains_dotfile_parent;
508 # continue; # skip to next item (i.e. don't add to file_list_pruned)
509 # fi;
510
511 # ### Ignore dotfiles themselves
512 # item_basename="$(basename "$item")";
513 # pattern="^\.";
514 # if [[ $item_basename =~ $pattern ]]; then
515 # vbm "INFO :Skipping dotfile:item:$item";
516 # continue; # skip to next item
517 # fi;
b64bbf81 518 fi;
3434f509 519
b64bbf81
SBS
520 ## Ignore files with newlines present in filename. See [2].
521 if [[ $item =~ $'\n' ]]; then
90152692 522 vbm "DEBUG:Skipping file name with newline:$item";
b64bbf81
SBS
523 continue; # skip to next item
524 fi;
525
526 ## Ignore files that end in '~'.
527 if [[ $item =~ ~$ ]]; then
90152692 528 vbm "DEBUG:Skipping file ending in tilde:$item";
b64bbf81
SBS
529 continue; # skip to next item
530 fi;
fdf23d9f 531
3434f509
SBS
532 ## Ignore files modified less than age_threshold. See [3].
533 time_now="$(date +%s)"; # epoch seconds
534 item_age="$(stat -c %Y "$item")"; # age in seconds
535 if [[ $(( time_now - item_age )) -lt "$age_threshold" ]]; then
536 yell "INFO :Skipping file modified less than $age_threshold seconds ago:item:$item";
fdf23d9f
SBS
537 continue; # skip to next item
538 fi;
b64bbf81
SBS
539
540 ## Add item to file_list_pruned
541 file_list_pruned+=("$item");
542 done;
543 if [[ $opVerbose == "true" ]]; then
544 vbm "DEBUG:file_list_pruned:";
545 printf "%s\n" "${file_list_pruned[@]}";
546 fi;
547
548 # Decide what actions to take for items in file_list_pruned
549 for item in "${file_list_pruned[@]}"; do
550 vbm "DEBUG:considering action to take for item:$item";
551 unset path_src path_prf dir_parent dir_source;
552
553 ## Check file extension
554 if [[ $item =~ .ots$ ]]; then
555 ### item ends in '.ots'. Item is proof file.
556 vbm "DEBUG:item ends in '.ots'. Item is proof file:item:$item";
557 if [[ -f ${item%.ots} ]]; then
558 #### Proof file (item) is adjacent to source file
559 vbm "DEBUG:Proof file (item) is adjacent to source file.";
560 ##### Upgrade and verify proof file against adjacent source file
561 vbm "DEBUG:Marking proof file to be upgraded and verified.";
562 path_src="${item%.ots}";
563 path_prf="$item";
564 files_to_upgrade+=("$(printf "%s" "$path_prf")");
565 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
566 else
567 #### Proof file (item) is not adjacent to source file
568 vbm "DEBUG:Proof file (item) is not adjacent to source file.";
569 #### Check if source file in parent dir
570 dir_parent="$(dirname "$(dirname "$item")" )";
571 cand_src_filename="$(basename "$item")";
572 cand_src_path="$dir_parent/$cand_src_filename";
573 if [[ -f "$cand_src_path" ]]; then
574 ##### source file in parent dir
575 vbm "DEBUG:found source file in parent:cand_src_path:$cand_src_path";
576 path_src="$cand_src_path";
577 path_prf="$item";
578 files_to_upgrade+=("$(printf "%s" "$path_prf")");
579 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
580 else
581 #### Throw non-fatal error
582 vbm "DEBUG:Source file not found for proof file:item:$item";
583 yell "ERROR:Item is proof file but source filei not adjacent in parent dir. item:$item";
584 #### Attempt upgrade only
585 vbm "DEBUG:Marking proof file to be upgraded.";
586 path_prf="$item";
587 files_to_upgrade+=("$(printf "%s" "$path_prf")");
588 fi;
589 fi;
590 else
591 ### item does not end in '.ots'. Item is source file.
592 vbm "DEBUG:item does NOT end in '.ots'. Item is source file.";
593 if [[ -f "$item".ots ]]; then
594 #### Proof file is adjacent to source file (item).
595 vbm "DEBUG:Proof file is adjacent to source file (item).";
596 ##### Upgrade and verify proof file against adjacent source file.
597 vbm "DEBUG:Marking proof file to be upgraded and verified.";
598 path_src="$item";
599 path_prf="$item.ots";
600 files_to_upgrade+=("$(printf "%s" "$path_prf")");
601 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
602 else
603 #### Proof file is not adjacent to source file (item).
604 #### Check if proof file is in subdir
605 vbm "DEBUG:checking if proof file for source file (item) is in subdir:item:$item";
606 unset flag_proof_in_subdir;
607 dir_item="$(dirname "$item")";
608 cand_prf_filename="$(basename "$item")".ots;
609 while read -r line; do
610 line_basename="$(basename "$line")";
611 if [[ $line_basename == "$cand_prf_filename" ]]; then
612 flag_proof_in_subdir="true";
613 path_prf="$line";
614 vbm "DEBUG:proof found in subdir at:line:$line";
615 break;
616 fi;
617 done < <(find "$dir_item" -mindepth 2 -maxdepth 2 -type f)
618 if [[ $flag_proof_in_subdir == "true" ]]; then
619 ##### Proof file is in subdir
620 vbm "DEBUG:Proof file detected in subdir relative to source file (item)";
621 #path_prf="$path_prf"; # set in while loop
622 path_src="$item";
623 files_to_upgrade+=("$(printf "%s" "$path_prf")");
624 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
625 else
626 ##### Proof file is not in subdir
627 vbm "DEBUG:Proof file not detected in subdir relative to source file (item).";
628 #### Stamp source file
629 vbm "DEBUG:Marking source file to be stamped.";
630 path_src="$item";
631 files_to_stamp+=("$(printf "%s" "$path_src")")
632 fi;
633 unset flag_proof_in_subdir;
634 fi;
635 fi;
636 done;
637 unset path_src path_prf dir_item dir_parent cand_prf_filename cand_src_filename line_basename cand_src_path
638
639 # Prune action lists.
640 ## Sort and prune file action arrays
641 ### files to upgrade
642 while read -r -d $'\0' line; do
643 vbm "DEBUG:adding to files_to_upgrade_pruned:line:$line";
644 files_to_upgrade_pruned+=("$line");
645 done < <(printf "%s\0" "${files_to_upgrade[@]}" | sort -zu | shuf -z); # See [1]
646 if [[ $opVerbose == "true" ]]; then
647 vbm "DEBUG:files_to_upgrade_pruned:";
648 printf "%s\n" "${files_to_upgrade_pruned[@]}";
649 fi;
650
651 ### files to verify
652 while read -r -d $'\0' line; do
653 vbm "DEBUG:adding to files_to_verify_pruned:line:$line";
654 files_to_verify_pruned+=("$line");
655 done < <(printf "%s\0" "${files_to_verify[@]}" | sort -zu | shuf -z); # See [1]
656 if [[ $opVerbose == "true" ]]; then
657 vbm "DEBUG:files_to_verify_pruned:";
658 printf "%s\n\n" "${files_to_verify_pruned[@]}";
659 fi;
660
661 ### files to stamp
662 while read -r -d $'\0' line; do
663 vbm "DEBUG:adding to files_to_stamp_pruned:line:$line";
664 files_to_stamp_pruned+=("$line");
665 done < <(printf "%s\0" "${files_to_stamp[@]}" | sort -zu | shuf -z); # See [1]
666 if [[ $opVerbose == "true" ]]; then
667 vbm "DEBUG:files_to_stamp_pruned:";
668 printf "%s\n" "${files_to_stamp_pruned[@]}";
669 fi;
670
671 # Act on files
5ed29928 672 ## Assemble and execute upgrade file commands
b64bbf81
SBS
673 for item in "${files_to_upgrade_pruned[@]}"; do
674 path_prf="$(cut -d $'\n' -f1 < <(echo "$item"))";
90152692 675 path_prf_sesc="${path_prf//\"/\\\"}"; # escape path double quotes. See [4].
b64bbf81
SBS
676 if [[ -z "$path_prf" ]]; then
677 yell "ERROR:blank upgrade item encountered. Skipping:item:$item";
678 continue;
679 fi;
680 vbm "DEBUG:Attempting to upgrade proof file:path_prf:$path_prf";
681 if [[ ! $option_dry_run == "true" ]]; then
fdf23d9f
SBS
682 ### Try upgrade with known calendars in random order
683 while read -r url; do
684 vbm "DEBUG:Upgrading with calendar:url:$url";
5ed29928
SBS
685
686 #### assemble command
687 local -a cmd_temp;
688 cmd_temp+=("ots");
689 if [[ "$opVerbose" = "true" ]]; then cmd_temp+=("-v"); fi;
690 cmd_temp+=("-l" "$url" "--no-default-whitelist");
691 cmd_temp+=("upgrade" "$path_prf_sesc");
692 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp; fi;
693
694 #### execute command
695 wait_for_jobslot && must "${cmd_temp[@]}" &
696 unset cmd_temp;
697 break;
90152692 698 #ots -l "$url" --no-default-whitelist upgrade "$path_prf" && break;
fdf23d9f 699 done < <(printf "%s\n" "${calendars[@]}" | shuf);
b64bbf81 700 else
b108df29 701 yell "DEBUG:DRY RUN:Not running:\"ots upgrade $path_prf\"";
b64bbf81 702 fi;
b64bbf81
SBS
703 done;
704
5ed29928 705 ## Assemble and execute verify file commands
b64bbf81
SBS
706 for item in "${files_to_verify_pruned[@]}"; do
707 path_src="$(cut -d $'\n' -f1 < <(echo "$item"))";
708 path_prf="$(cut -d $'\n' -f2 < <(echo "$item"))";
90152692
SBS
709 path_src_sesc="${path_src//\"/\\\"}"; # escape path double quotes. See [4].
710 path_prf_sesc="${path_prf//\"/\\\"}"; # escape path double quotes. See [4].
b64bbf81
SBS
711 if [[ -z "$path_src" ]] || [[ -z "$path_prf" ]]; then
712 yell "ERROR:blank verify item encountered. Skipping:item:$item";
713 continue;
714 fi;
715 vbm "DEBUG:Attempting to verify source file:path_src:$path_src";
716 vbm "DEBUG: against proof file: path_prf:$path_prf";
717 if [[ ! $option_dry_run == "true" ]]; then
fdf23d9f
SBS
718 ### Try verify with known calendars in random order
719 while read -r url; do
720 vbm "DEBUG:Verifying with calendar:url:$url";
5ed29928
SBS
721
722 #### assemble command
723 local -a cmd_temp;
724 cmd_temp+=("ots");
725 if [[ "$opVerbose" = "true" ]]; then cmd_temp+=("-v"); fi;
726 cmd_temp+=("-l" "$url" "--no-default-whitelist");
727 cmd_temp+=("verify" "-f" "$path_src_sesc" "$path_prf_sesc");
728 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp; fi;
729
730 #### execute command
731 wait_for_jobslot && must "${cmd_temp[@]}" &
732 unset cmd_temp;
733 break;
90152692 734 #ots -l "$url" --no-default-whitelist verify -f "$path_src" "$path_prf" && break;
fdf23d9f 735 done < <(printf "%s\n" "${calendars[@]}" | shuf);
b64bbf81 736 else
b108df29 737 yell "DEBUG:DRY RUN:Not running:\"ots verify -f $path_src $path_prf\"";
b64bbf81 738 fi;
b64bbf81
SBS
739 done;
740
5ed29928 741 ## Assemble and execute stamp file commands
b64bbf81
SBS
742 for item in "${files_to_stamp_pruned[@]}"; do
743 path_src="$(cut -d $'\n' -f1 < <(echo "$item"))";
90152692 744 path_src_sesc="${path_src//\"/\\\"}"; # escape path double quotes. See [4].
b64bbf81
SBS
745 if [[ -z "$path_src" ]]; then
746 yell "ERROR:blank stamp item encountered. Skipping:item:$item";
747 continue;
748 fi;
749 vbm "DEBUG:Attempting to stamp source file:path_src:$path_src";
750 if [[ ! $option_dry_run == "true" ]]; then
5ed29928
SBS
751
752 #### assemble command
753 local -a cmd_temp;
754 cmd_temp+=("ots");
755 if [[ "$opVerbose" = "true" ]]; then cmd_temp+=("-v"); fi;
756 cmd_temp+=("stamp" "$path_src_sesc");
757 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp; fi;
758
759 #### execute command
760 wait_for_jobslot && must "${cmd_temp[@]}" &
761 unset cmd_temp;
90152692 762 #ots stamp "$path_src";
b64bbf81 763 else
b108df29 764 yell "DEBUG:DRY RUN:Not running:\"ots stamp $path_src\"";
b64bbf81 765 fi;
b64bbf81 766 done;
b64bbf81
SBS
767}; # main program
768
769# Run program
770main "$@";
3904c727 771exit 0;