a7f61ee8414a158253fe1964bd93f5d1156a6296
[BK-2020-03.git] / user / bkots
1 #!/usr/bin/env bash
2
3 # Define variables
4 declare -Ag appRollCall # Associative array for storing app status
5 declare -Ag fileRollCall # Associative array for storing file status
6 declare -Ag dirRollCall # Associative array for storing dir status
7 declare -ag arrayPosArgs # Associative array for processArgs() function
8 declare -ag calendars;
9 calendars+=("https://finney.calendar.eternitywall.com");
10 calendars+=("https://btc.calendar.catallaxy.com");
11 calendars+=("https://alice.btc.calendar.opentimestamps.org");
12 calendars+=("https://bob.btc.calendar.opentimestamps.org");
13 age_threshold="60"; # min age to add file; seconds;
14 max_job_count="2"; # default max job count
15
16 # Declare functions
17 yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
18 die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
19 must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
20 checkapp() {
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
46 checkfile() {
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
73 checkdir() {
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
102 displayMissing() {
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
174 vbm() {
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
191 showVersion() {
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'
203 bkots 2.0.2
204 Copyright (C) 2022 Steven Baltakatei Sandoval
205 License GPLv3: GNU GPL version 3
206 This is free software; you are free to change and redistribute it.
207 There 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.
214 EOF
215
216 # End function
217 vbm "DEBUG:showVersion function ended."
218 return 0; # Function finished.
219 } # Display script version.
220 showUsage() {
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).
242 -j, --jobs
243 Specify simultaneous job count (default: 2)
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
266 Files modified less than 1 minute ago are ignored.
267
268 EXAMPLES:
269 bkots -v foo.txt
270 bkots foo.txt bar.pdf /home/username/Pictures/
271 EOF
272 } # Display information on how to use this script.
273 count_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
288 wait_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 };
299 processArgs() {
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";;
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;;
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.).
370 get_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
376 # Depends: yell(), die(), must()
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
394 main() {
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 "$@";
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
404 # Output: file(s) creates `.ots` file alongside specified files
405 # Depends: find (GNU findutils) 4.8.0, GNU Coreutils 8.32 (sort), GNU Parallel 20210822
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
408 # [3] Get mtime of specific file using Bash? https://stackoverflow.com/a/4774377
409 local -a file_list file_list_pruned;
410 local -a files_to_verify files_to_upgrade files_to_stamp
411 local -a files_to_verify_pruned files_to_upgrade_pruned files_to_stamp_pruned
412
413 # Process args
414 processArgs "$@";
415
416 # Check dependencies
417 if ! checkapp ots find parallel; then
418 displayMissing;
419 die "FATAL:Missing dependencies.";
420 fi;
421
422 # Check arguments
423 for arg in "${arrayPosArgs[@]}"; do
424 arg="$(readlink -f "$arg")";
425 if ! { [[ -d $arg ]] || [[ -f $arg ]]; }; then
426 die "FATAL:Not a file or dir:arg:$arg";
427 fi;
428 done;
429
430 # Display ots details
431 vbm "$(type ots)"; # show how 'ots' is defined
432 #TODO: add option to define 'ots' as a bash function that
433 #populates the ots option '--bitcoin-node FILE' with a
434 #user-specified FILE.
435
436 # Populate file_list
437 vbm "DEBUG:begin populate file_list array";
438 for item in "${arrayPosArgs[@]}"; do
439 vbm "DEBUG:adding to file list:item:$item";
440
441 ## Get full canonicalized path (follow symlinks)
442 item="$(readlink -f "$item")";
443 vbm "DEBUG:item full path:item:$item";
444
445 ## Add to list: files
446 if [[ -f $item ]]; then
447 vbm "DEBUG:is a file:item:$item";
448 file_list+=("$item");
449 vbm "DEBUG:added to file_list:$item";
450 ## Add to list: files in dirs
451 elif [[ -d $item ]]; then
452 vbm "DEBUG:is a dir:item:$item";
453 ### Check for recursive flag
454 if [[ "$option_recursive" == "true" ]]; then
455 vbm "DEBUG:option_recursive:$option_recursive";
456 while read -r line; do
457 file_list+=("$line");
458 vbm "DEBUG:added to file_list:$line";
459 done < <(find "$item" -type f);
460 else
461 while read -r line; do
462 file_list+=("$line");
463 vbm "DEBUG:added to file_list:$line";
464 done < <(find "$item" -maxdepth 1 -type f);
465 fi;
466 else
467 die "FATAL:Not a file or dir:item:$item";
468 fi;
469 done;
470 if [[ $opVerbose == "true" ]]; then
471 vbm "DEBUG:file_list:";
472 printf "%s\n" "${file_list[@]}";
473 fi;
474
475 # Prune file_list
476 for item in "${file_list[@]}"; do
477 ## Ignore files that end in '.ots.bak'.
478 if [[ $item =~ '.ots.bak'$ ]]; then
479 vbm "DEBUG:Skipping file ending in '.ots.bak':item:$item";
480 continue; # skip to next item
481 fi;
482
483 ## Ignore dotfiles
484 if ! [[ $option_include_dotfiles == "true" ]]; then
485 ### Ignore files if '/.' contained within canonical path
486 pattern="/\."; # a dot after a forward slash
487 if [[ $item =~ $pattern ]]; then
488 continue;
489 fi;
490
491 # ### Ignore files located beneath a dotfile directory (e.g. '/home/my_repo/.git/config')
492 # unset flag_contains_dotfile_parent;
493 # while read -r line; do
494 # #### Check line from output of get_parent_dirnames
495 # pattern="^\.";
496 # if [[ $line =~ $pattern ]]; then
497 # ##### line starts with '.'
498 # vbm "DEBUG:Dotfile parent detected. Not including in file_list_pruned:$item";
499 # vbm "DEBUG:Dotfile in path:item:$item";
500 # vbm "DEBUG:Dotfile parent:line:$line";
501 # flag_contains_dotfile_parent="true";
502 # break
503 # fi;
504 # done < <(get_parent_dirnames "$item");
505 # if [[ $flag_contains_dotfile_parent == "true" ]]; then
506 # unset flag_contains_dotfile_parent;
507 # continue; # skip to next item (i.e. don't add to file_list_pruned)
508 # fi;
509
510 # ### Ignore dotfiles themselves
511 # item_basename="$(basename "$item")";
512 # pattern="^\.";
513 # if [[ $item_basename =~ $pattern ]]; then
514 # vbm "INFO :Skipping dotfile:item:$item";
515 # continue; # skip to next item
516 # fi;
517 fi;
518
519 ## Ignore files with newlines present in filename. See [2].
520 if [[ $item =~ $'\n' ]]; then
521 vbm "DEBUG:Skipping file name with newline:$item";
522 continue; # skip to next item
523 fi;
524
525 ## Ignore files that end in '~'.
526 if [[ $item =~ ~$ ]]; then
527 vbm "DEBUG:Skipping file ending in tilde:$item";
528 continue; # skip to next item
529 fi;
530
531 ## Ignore files modified less than age_threshold. See [3].
532 time_now="$(date +%s)"; # epoch seconds
533 item_age="$(stat -c %Y "$item")"; # age in seconds
534 if [[ $(( time_now - item_age )) -lt "$age_threshold" ]]; then
535 yell "INFO :Skipping file modified less than $age_threshold seconds ago:item:$item";
536 continue; # skip to next item
537 fi;
538
539 ## Add item to file_list_pruned
540 file_list_pruned+=("$item");
541 done;
542 if [[ $opVerbose == "true" ]]; then
543 vbm "DEBUG:file_list_pruned:";
544 printf "%s\n" "${file_list_pruned[@]}";
545 fi;
546
547 # Decide what actions to take for items in file_list_pruned
548 for item in "${file_list_pruned[@]}"; do
549 vbm "DEBUG:considering action to take for item:$item";
550 unset path_src path_prf dir_parent dir_source;
551
552 ## Check file extension
553 if [[ $item =~ .ots$ ]]; then
554 ### item ends in '.ots'. Item is proof file.
555 vbm "DEBUG:item ends in '.ots'. Item is proof file:item:$item";
556 if [[ -f ${item%.ots} ]]; then
557 #### Proof file (item) is adjacent to source file
558 vbm "DEBUG:Proof file (item) is adjacent to source file.";
559 ##### Upgrade and verify proof file against adjacent source file
560 vbm "DEBUG:Marking proof file to be upgraded and verified.";
561 path_src="${item%.ots}";
562 path_prf="$item";
563 files_to_upgrade+=("$(printf "%s" "$path_prf")");
564 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
565 else
566 #### Proof file (item) is not adjacent to source file
567 vbm "DEBUG:Proof file (item) is not adjacent to source file.";
568 #### Check if source file in parent dir
569 dir_parent="$(dirname "$(dirname "$item")" )";
570 cand_src_filename="$(basename "$item")";
571 cand_src_path="$dir_parent/$cand_src_filename";
572 if [[ -f "$cand_src_path" ]]; then
573 ##### source file in parent dir
574 vbm "DEBUG:found source file in parent:cand_src_path:$cand_src_path";
575 path_src="$cand_src_path";
576 path_prf="$item";
577 files_to_upgrade+=("$(printf "%s" "$path_prf")");
578 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
579 else
580 #### Throw non-fatal error
581 vbm "DEBUG:Source file not found for proof file:item:$item";
582 yell "ERROR:Item is proof file but source filei not adjacent in parent dir. item:$item";
583 #### Attempt upgrade only
584 vbm "DEBUG:Marking proof file to be upgraded.";
585 path_prf="$item";
586 files_to_upgrade+=("$(printf "%s" "$path_prf")");
587 fi;
588 fi;
589 else
590 ### item does not end in '.ots'. Item is source file.
591 vbm "DEBUG:item does NOT end in '.ots'. Item is source file.";
592 if [[ -f "$item".ots ]]; then
593 #### Proof file is adjacent to source file (item).
594 vbm "DEBUG:Proof file is adjacent to source file (item).";
595 ##### Upgrade and verify proof file against adjacent source file.
596 vbm "DEBUG:Marking proof file to be upgraded and verified.";
597 path_src="$item";
598 path_prf="$item.ots";
599 files_to_upgrade+=("$(printf "%s" "$path_prf")");
600 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
601 else
602 #### Proof file is not adjacent to source file (item).
603 #### Check if proof file is in subdir
604 vbm "DEBUG:checking if proof file for source file (item) is in subdir:item:$item";
605 unset flag_proof_in_subdir;
606 dir_item="$(dirname "$item")";
607 cand_prf_filename="$(basename "$item")".ots;
608 while read -r line; do
609 line_basename="$(basename "$line")";
610 if [[ $line_basename == "$cand_prf_filename" ]]; then
611 flag_proof_in_subdir="true";
612 path_prf="$line";
613 vbm "DEBUG:proof found in subdir at:line:$line";
614 break;
615 fi;
616 done < <(find "$dir_item" -mindepth 2 -maxdepth 2 -type f)
617 if [[ $flag_proof_in_subdir == "true" ]]; then
618 ##### Proof file is in subdir
619 vbm "DEBUG:Proof file detected in subdir relative to source file (item)";
620 #path_prf="$path_prf"; # set in while loop
621 path_src="$item";
622 files_to_upgrade+=("$(printf "%s" "$path_prf")");
623 files_to_verify+=("$(printf "%s\n%s" "$path_src" "$path_prf")");
624 else
625 ##### Proof file is not in subdir
626 vbm "DEBUG:Proof file not detected in subdir relative to source file (item).";
627 #### Stamp source file
628 vbm "DEBUG:Marking source file to be stamped.";
629 path_src="$item";
630 files_to_stamp+=("$(printf "%s" "$path_src")")
631 fi;
632 unset flag_proof_in_subdir;
633 fi;
634 fi;
635 done;
636 unset path_src path_prf dir_item dir_parent cand_prf_filename cand_src_filename line_basename cand_src_path
637
638 # Prune action lists.
639 ## Sort and prune file action arrays
640 ### files to upgrade
641 while read -r -d $'\0' line; do
642 vbm "DEBUG:adding to files_to_upgrade_pruned:line:$line";
643 files_to_upgrade_pruned+=("$line");
644 done < <(printf "%s\0" "${files_to_upgrade[@]}" | sort -zu | shuf -z); # See [1]
645 if [[ $opVerbose == "true" ]]; then
646 vbm "DEBUG:files_to_upgrade_pruned:";
647 printf "%s\n" "${files_to_upgrade_pruned[@]}";
648 fi;
649
650 ### files to verify
651 while read -r -d $'\0' line; do
652 vbm "DEBUG:adding to files_to_verify_pruned:line:$line";
653 files_to_verify_pruned+=("$line");
654 done < <(printf "%s\0" "${files_to_verify[@]}" | sort -zu | shuf -z); # See [1]
655 if [[ $opVerbose == "true" ]]; then
656 vbm "DEBUG:files_to_verify_pruned:";
657 printf "%s\n\n" "${files_to_verify_pruned[@]}";
658 fi;
659
660 ### files to stamp
661 while read -r -d $'\0' line; do
662 vbm "DEBUG:adding to files_to_stamp_pruned:line:$line";
663 files_to_stamp_pruned+=("$line");
664 done < <(printf "%s\0" "${files_to_stamp[@]}" | sort -zu | shuf -z); # See [1]
665 if [[ $opVerbose == "true" ]]; then
666 vbm "DEBUG:files_to_stamp_pruned:";
667 printf "%s\n" "${files_to_stamp_pruned[@]}";
668 fi;
669
670 # Act on files
671 ## Assemble and execute upgrade file commands
672 for item in "${files_to_upgrade_pruned[@]}"; do
673 path_prf="$(cut -d $'\n' -f1 < <(echo "$item"))";
674 if [[ -z "$path_prf" ]]; then
675 yell "ERROR:blank upgrade item encountered. Skipping:item:$item";
676 continue;
677 fi;
678 vbm "DEBUG:Attempting to upgrade proof file:path_prf:$path_prf";
679 if [[ ! $option_dry_run == "true" ]]; then
680 ### Try upgrade with known calendars in random order
681 while read -r url; do
682 vbm "DEBUG:Upgrading with calendar:url:$url";
683
684 #### assemble command
685 local -a cmd_temp;
686 cmd_temp+=("ots");
687 if [[ "$opVerbose" = "true" ]]; then cmd_temp+=("-v"); fi;
688 cmd_temp+=("-l" "$url" "--no-default-whitelist");
689 cmd_temp+=("upgrade" "$path_prf");
690 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp; fi;
691
692 #### execute command
693 wait_for_jobslot && "${cmd_temp[@]}" &
694 unset cmd_temp;
695 break;
696 #ots -l "$url" --no-default-whitelist upgrade "$path_prf" && break;
697 done < <(printf "%s\n" "${calendars[@]}" | shuf);
698 else
699 yell "DEBUG:DRY RUN:Not running:\"ots upgrade $path_prf\"";
700 fi;
701 done;
702
703 ## Assemble and execute verify file commands
704 for item in "${files_to_verify_pruned[@]}"; do
705 path_src="$(cut -d $'\n' -f1 < <(echo "$item"))";
706 path_prf="$(cut -d $'\n' -f2 < <(echo "$item"))";
707 if [[ -z "$path_src" ]] || [[ -z "$path_prf" ]]; then
708 yell "ERROR:blank verify item encountered. Skipping:item:$item";
709 continue;
710 fi;
711 vbm "DEBUG:Attempting to verify source file:path_src:$path_src";
712 vbm "DEBUG: against proof file: path_prf:$path_prf";
713 if [[ ! $option_dry_run == "true" ]]; then
714 ### Try verify with known calendars in random order
715 while read -r url; do
716 vbm "DEBUG:Verifying with calendar:url:$url";
717
718 #### assemble command
719 local -a cmd_temp;
720 cmd_temp+=("ots");
721 if [[ "$opVerbose" = "true" ]]; then cmd_temp+=("-v"); fi;
722 cmd_temp+=("-l" "$url" "--no-default-whitelist");
723 cmd_temp+=("verify" "-f" "$path_src" "$path_prf");
724 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp; fi;
725
726 #### execute command
727 wait_for_jobslot && "${cmd_temp[@]}" &
728 unset cmd_temp;
729 break;
730 #ots -l "$url" --no-default-whitelist verify -f "$path_src" "$path_prf" && break;
731 done < <(printf "%s\n" "${calendars[@]}" | shuf);
732 else
733 yell "DEBUG:DRY RUN:Not running:\"ots verify -f $path_src $path_prf\"";
734 fi;
735 done;
736
737 ## Assemble and execute stamp file commands
738 for item in "${files_to_stamp_pruned[@]}"; do
739 path_src="$(cut -d $'\n' -f1 < <(echo "$item"))";
740 if [[ -z "$path_src" ]]; then
741 yell "ERROR:blank stamp item encountered. Skipping:item:$item";
742 continue;
743 fi;
744 vbm "DEBUG:Attempting to stamp source file:path_src:$path_src";
745 if [[ ! $option_dry_run == "true" ]]; then
746
747 #### assemble command
748 local -a cmd_temp;
749 cmd_temp+=("ots");
750 if [[ "$opVerbose" = "true" ]]; then cmd_temp+=("-v"); fi;
751 cmd_temp+=("stamp" "$path_src");
752 if [[ "$opVerbose" = "true" ]]; then declare -p cmd_temp; fi;
753
754 #### execute command
755 wait_for_jobslot && "${cmd_temp[@]}" &
756 unset cmd_temp;
757 #ots stamp "$path_src";
758 else
759 yell "DEBUG:DRY RUN:Not running:\"ots stamp $path_src\"";
760 fi;
761 done;
762 }; # main program
763
764 # Run program
765 main "$@";
766 exit 0;