Commit | Line | Data |
---|---|---|
b64bbf81 SBS |
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 | |
3a8398d2 SBS |
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"); | |
3434f509 | 13 | age_threshold="60"; # min age to add file; seconds; |
5ed29928 | 14 | max_job_count="2"; # default max job count |
b64bbf81 SBS |
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 | |
5ed29928 | 19 | must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails |
b64bbf81 SBS |
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' | |
26ab84d9 | 203 | bkots 2.0.1 |
b64bbf81 SBS |
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). | |
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/ | |
271 | EOF | |
272 | } # Display information on how to use this script. | |
5ed29928 SBS |
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 | }; | |
b64bbf81 SBS |
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";; | |
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.). | |
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 | |
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 | |
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 "$@"; | |
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 | |
26ab84d9 | 695 | wait_for_jobslot && "${cmd_temp[@]}" & |
5ed29928 SBS |
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 | |
26ab84d9 | 731 | wait_for_jobslot && "${cmd_temp[@]}" & |
5ed29928 SBS |
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 | |
26ab84d9 | 760 | wait_for_jobslot && "${cmd_temp[@]}" & |
5ed29928 | 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 | |
770 | main "$@"; | |
3904c727 | 771 | exit 0; |