-
-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathcommon-functions.sh
2521 lines (2164 loc) · 84.4 KB
/
common-functions.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# shellcheck disable=SC2001
# This file contains common functions that the scripts will source.
set -u
# -----------------------------------------------------------------------------
# CONSTANTS
# -----------------------------------------------------------------------------
FIELD_SEPARATOR=$'\r' # The main field separator.
GUI_BOX_HEIGHT=550 # Height of the GUI dialog boxes.
GUI_BOX_WIDTH=900 # Width of the GUI dialog boxes.
GUI_INFO_WIDTH=400 # Width of the GUI small dialog boxes.
IGNORE_FIND_PATH="*.git/*" # Path to ignore in the 'find' command.
PREFIX_ERROR_LOG_FILE="Errors" # Name of 'error' directory.
PREFIX_OUTPUT_DIR="Output" # Name of 'output' directory.
TEMP_DIR=$(mktemp --directory) # Temp directories for use in scripts.
TEMP_DIR_ITEMS_TO_REMOVE="$TEMP_DIR/items_to_remove"
TEMP_DIR_LOGS="$TEMP_DIR/logs"
TEMP_DIR_STORAGE_TEXT="$TEMP_DIR/storage_text"
TEMP_DIR_TASK="$TEMP_DIR/task"
WAIT_BOX_CONTROL_KDE="$TEMP_DIR/wait_box_control_kde"
WAIT_BOX_CONTROL="$TEMP_DIR/wait_box_control"
WAIT_BOX_FIFO="$TEMP_DIR/wait_box_fifo"
readonly \
FIELD_SEPARATOR \
GUI_BOX_HEIGHT \
GUI_BOX_WIDTH \
GUI_INFO_WIDTH \
IGNORE_FIND_PATH \
PREFIX_ERROR_LOG_FILE \
PREFIX_OUTPUT_DIR \
TEMP_DIR \
TEMP_DIR_LOGS \
TEMP_DIR_STORAGE_TEXT \
TEMP_DIR_TASK \
WAIT_BOX_CONTROL \
WAIT_BOX_CONTROL_KDE \
WAIT_BOX_FIFO
# -----------------------------------------------------------------------------
# GLOBAL VARIABLES
# -----------------------------------------------------------------------------
IFS=$FIELD_SEPARATOR
INPUT_FILES=$*
TEMP_DATA_TASK=""
# -----------------------------------------------------------------------------
# BUILD THE STRUCTURE OF THE 'TEMP_DIR'
# -----------------------------------------------------------------------------
# Store the path of temporary items to be removed after exit.
mkdir -p "$TEMP_DIR_ITEMS_TO_REMOVE"
mkdir -p "$TEMP_DIR_LOGS" # Store 'error logs'.
mkdir -p "$TEMP_DIR_STORAGE_TEXT" # Store the output from parallel processes.
mkdir -p "$TEMP_DIR_TASK" # Store temporary files of scripts.
# -----------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------
_cleanup_on_exit() {
# This function performs cleanup tasks when the script exits. It is
# designed to safely and efficiently remove temporary directories or files
# that were created during the script's execution.
# Remove local temporary dirs or files.
local items_to_remove=""
items_to_remove=$(cat -- "$TEMP_DIR_ITEMS_TO_REMOVE/"* 2>/dev/null)
# Escape single quotes in filenames to handle them correctly in 'xargs'
# with 'bash -c'.
items_to_remove=$(sed -z "s|'|'\\\''|g" <<<"$items_to_remove")
printf "%s" "$items_to_remove" | xargs \
--no-run-if-empty \
--delimiter="$FIELD_SEPARATOR" \
--max-procs="$(_get_max_procs)" \
--replace="{}" \
bash -c "{ chmod -R u+rw -- '{}' && rm -rf -- '{}'; } &>/dev/null"
# Remove the main temporary dir.
rm -rf -- "$TEMP_DIR" &>/dev/null
if ! _is_gui_session; then
printf "End of the script.\n" >&2
fi
}
trap _cleanup_on_exit EXIT
_check_dependencies() {
# This function ensures that all required dependencies are available for
# the scripts to run. It verifies the presence of specified commands or
# packages and prompts the user to install missing ones.
#
# Parameters:
# - $1 (dependencies): A list of dependencies to check, formatted as a
# "|" delimited string. Each dependency can specify:
# - "command": The name of a command to check for in the shell.
# - "package": The package associated with the command (if different).
# - "pkg_manager": Optional. The specific package manager.
#
# Example:
# - _check_dependencies "
# command=ffmpeg; pkg_manager=apt; package=ffmpeg |
# command=ffmpeg; pkg_manager=dnf; package=ffmpeg-free |
# command=ffmpeg; pkg_manager=pacman; package=ffmpeg |
# command=ffmpeg; pkg_manager=zypper; package=ffmpeg"
local dependencies=$1
local packages_to_install=""
local pkg_manager_installed=""
[[ -z "$dependencies" ]] && return
# Skip duplicated dependencies in the input list.
dependencies=$(tr "|" "\n" <<<"$dependencies")
dependencies=$(tr -d " " <<<"$dependencies")
dependencies=$(sort --unique <<<"$dependencies")
dependencies=$(_text_remove_empty_lines "$dependencies")
[[ -z "$dependencies" ]] && return
# Get the name of the installed package manager.
pkg_manager_installed=$(_pkg_get_package_manager)
if [[ -z "$pkg_manager_installed" ]]; then
_display_error_box "Could not find a package manager!"
_exit_script
fi
# Check all dependencies.
dependencies=$(tr "\n" "$FIELD_SEPARATOR" <<<"$dependencies")
local dependency=""
for dependency in $dependencies; do
local command=""
local package=""
local pkg_manager=""
# Evaluate the values parameters from the 'dependency' variable.
eval "$dependency"
# Ignore installing the dependency if there is a command in the shell.
if [[ -n "$command" ]] && _command_exists "$command"; then
continue
fi
# Ignore installing the dependency if the installed
# package managers differ.
if [[ -n "$pkg_manager" ]] &&
[[ "$pkg_manager_installed" != "$pkg_manager" ]]; then
continue
fi
# Ignore installing the dependency if the package is already installed
# (packages that do not have a command).
if [[ -n "$package" ]] && [[ -z "$command" ]] &&
_pkg_is_package_installed "$pkg_manager_installed" "$package"; then
continue
fi
# If the package is not specified, use the command name as
# the package name.
if [[ -z "$package" ]] && [[ -n "$command" ]]; then
package=$command
fi
# Add the package to the list to install.
if [[ -n "$package" ]]; then
packages_to_install+=" $package"
fi
done
# Ask the user to install the packages.
if [[ -n "$packages_to_install" ]]; then
local message="These packages were not found:"
message+=$(sed "s| |\n- |g" <<<"$packages_to_install")
message+=$'\n'$'\n'
message+="Would you like to install them?"
if _display_question_box "$message"; then
_pkg_install_packages \
"$pkg_manager_installed" "${packages_to_install/ /}"
else
_exit_script
fi
fi
}
_check_output() {
# This function validates the success of a command or process based on its
# exit code and output. It logs errors if the command fails or if an
# expected output file is missing.
#
# Parameters:
# - $1 (exit_code): The exit code returned by the command or process.
# - $2 (std_output): The standard output or error from the command.
# - $3 (input_file): The input file associated (if applicable).
# - $4 (output_file): The expected output file to verify its existence.
#
# Returns:
# - "0" (true): If the command was successful and the output file exists.
# - "1" (false): If the command failed or the output file does not exist.
local exit_code=$1
local std_output=$2
local input_file=$3
local output_file=$4
# Check the 'exit_code' and log the error.
if ((exit_code != 0)); then
_log_error "Command failed with a non-zero exit code." \
"$input_file" "$std_output" "$output_file"
return 1
fi
# Check if the output file exists.
if [[ -n "$output_file" ]] && [[ ! -e "$output_file" ]]; then
_log_error "The output file does not exist." \
"$input_file" "$std_output" "$output_file"
return 1
fi
return 0
}
_command_exists() {
# This function checks whether a given command is available on the system.
#
# Parameters:
# - $1 (command_check): The name of the command to verify.
#
# Returns:
# - "0" (true): If the command is available.
# - "1" (false): If the command is not available.
local command_check=$1
if command -v "$command_check" &>/dev/null; then
return 0
fi
return 1
}
_convert_delimited_string_to_text() {
# This function converts a delimited string of items into
# newline-separated text.
#
# Parameters:
# - $1 (input_items): A string containing items separated by the
# '$FIELD_SEPARATOR' variable.
#
# Returns:
# - A string containing the items separated by newlines.
local input_items=$1
local new_line="'\$'\\\n''"
input_items=$(sed -z "s|\n|$new_line|g; s|$new_line$||g" <<<"$input_items")
input_items=$(tr "$FIELD_SEPARATOR" "\n" <<<"$input_items")
printf "%s" "$input_items"
}
_directory_pop() {
# This function pops the top directory off the directory stack and changes
# to the previous directory.
#
# Returns:
# - "0" (true): If the directory was successfully popped and changed.
# - "1" (false): If there was an error popping the directory.
popd &>/dev/null || {
_log_error "Could not pop a directory." "" "" ""
return 1
}
return 0
}
_directory_push() {
# This function pushes the specified directory onto the directory stack and
# changes to it.
#
# Parameters:
# - $1 (directory): The target directory to push onto the directory stack
# and navigate to.
#
# Returns:
# - "0" (true): If the directory was successfully pushed and changed.
# - "1" (false): If there was an error pushing the directory.
local directory=$1
pushd "$directory" &>/dev/null || {
_log_error "Could not push the directory '$directory'." "" "" ""
return 1
}
return 0
}
_convert_text_to_delimited_string() {
# This function converts newline-separated text into a delimited string of
# items.
#
# Parameters:
# - $1 (input_items): A string containing items separated by newlines.
#
# Returns:
# - A string containing the items separated by the '$FIELD_SEPARATOR'
# variable.
local input_items=$1
local new_line="'\$'\\\n''"
input_items=$(tr "\n" "$FIELD_SEPARATOR" <<<"$input_items")
input_items=$(sed -z "s|$new_line|\n|g" <<<"$input_items")
input_items=$(_str_remove_empty_tokens "$input_items")
printf "%s" "$input_items"
}
_display_dir_selection_box() {
# This function presents a graphical interface to allow the user to select
# one or more directories.
local input_files=""
if _command_exists "zenity"; then
input_files=$(zenity --title "$(_get_script_name)" \
--file-selection --multiple --directory \
--separator="$FIELD_SEPARATOR" 2>/dev/null) || _exit_script
elif _command_exists "kdialog"; then
input_files=$(kdialog --title "$(_get_script_name)" \
--getexistingdirectory 2>/dev/null) || _exit_script
# Use parameter expansion to remove the last space.
input_files=${input_files% }
input_files=${input_files// \//$FIELD_SEPARATOR/}
fi
input_files=$(_str_remove_empty_tokens "$input_files")
printf "%s" "$input_files"
}
# shellcheck disable=SC2120
_display_file_selection_box() {
# This function presents a graphical interface to allow the user to select
# a file.
#
# Parameters:
# - $1 (file_filter): Optional. File filter pattern to restrict the types
# of files shown.
# - $2 (title): Optional. Title of the window.
local file_filter=${1:-""}
local title=${2:-"$(_get_script_name)"}
local input_files=""
if _command_exists "zenity"; then
input_files=$(zenity --title "$title" \
--file-selection \
${file_filter:+--file-filter="$file_filter"} \
--separator="$FIELD_SEPARATOR" 2>/dev/null) || _exit_script
elif _command_exists "kdialog"; then
input_files=$(kdialog --title "$title" \
--getopenfilename 2>/dev/null) || _exit_script
# Use parameter expansion to remove the last space.
input_files=${input_files% }
input_files=${input_files// \//$FIELD_SEPARATOR/}
fi
input_files=$(_str_remove_empty_tokens "$input_files")
printf "%s" "$input_files"
}
_display_error_box() {
# This function displays an error message to the user, adapting to the
# available environment.
#
# Parameters:
# - $1 (message): The error message to display.
local message=$1
if ! _is_gui_session; then
printf "Error: %s\n" "$message" >&2
elif [[ -n "$DBUS_SESSION_BUS_ADDRESS" ]]; then
_gdbus_notify "dialog-error" "$(_get_script_name)" "$message"
elif _command_exists "zenity"; then
zenity --title "$(_get_script_name)" --error \
--width="$GUI_INFO_WIDTH" --text "$message" &>/dev/null
elif _command_exists "kdialog"; then
kdialog --title "$(_get_script_name)" --error "$message" &>/dev/null
elif _command_exists "xmessage"; then
xmessage -title "$(_get_script_name)" "Error: $message" &>/dev/null
fi
}
_display_info_box() {
# This function displays an information message to the user, adapting to
# the available environment.
#
# Parameters:
# - $1 (message): The information message to display.
local message=$1
if ! _is_gui_session; then
printf "Info: %s\n" "$message" >&2
elif [[ -n "$DBUS_SESSION_BUS_ADDRESS" ]]; then
_gdbus_notify "dialog-information" "$(_get_script_name)" "$message"
elif _command_exists "zenity"; then
zenity --title "$(_get_script_name)" --info \
--width="$GUI_INFO_WIDTH" --text "$message" &>/dev/null
elif _command_exists "kdialog"; then
kdialog --title "$(_get_script_name)" --msgbox "$message" &>/dev/null
elif _command_exists "xmessage"; then
xmessage -title "$(_get_script_name)" "Info: $message" &>/dev/null
fi
}
_display_list_box() {
# This function displays a list box with selectable items, adapting to the
# available environment.
#
# Parameters:
# - $1 (message): A string containing the items to display in the list.
# - $2 (columns): Column definitions for the list, typically in the
# format "--column=<name>;--column=<name>".
# - $3 (item_name): A string representing the name of the items in the
# list. If not provided, the default value is "items".
# - $4 (resolve_links): A boolean-like string ("true" or "false")
# indicating whether symbolic links in item paths should be resolved to
# their target locations when opening the item's location. Defaults to
# "true".
local message=$1
local columns=$2
local item_name=${3:-"items"}
local resolve_links=${4:-"true"}
local columns_count=0
local items_count=0
local selected_item=""
local message_select=""
_close_wait_box
_logs_consolidate ""
if [[ -n "$message" ]]; then
items_count=$(tr -cd "\n" <<<"$message" | wc -c)
message_select=" Select an item to open its location:"
fi
# Count the number of columns.
columns_count=$(grep --only-matching "column=" <<<"$columns" | wc -l)
if ! _is_gui_session; then
if [[ -z "$message" ]]; then
message="(Empty result)"
printf "%s\n" "$message" >&2
else
message=$(tr "$FIELD_SEPARATOR" " " <<<"$message")
printf "%s\n" "$message"
fi
elif _command_exists "zenity"; then
if [[ -z "$message" ]]; then
# NOTE: Some versions of Zenity crash if the
# message is empty (Segmentation fault).
message=" "
fi
columns=$(tr ";" "$FIELD_SEPARATOR" <<<"$columns")
message=$(tr "\n" "$FIELD_SEPARATOR" <<<"$message")
# shellcheck disable=SC2086
selected_item=$(zenity --title "$(_get_script_name)" --list \
--editable --multiple --separator="$FIELD_SEPARATOR" \
--width="$GUI_BOX_WIDTH" --height="$GUI_BOX_HEIGHT" \
--print-column "$columns_count" \
--text "Total of $items_count $item_name.$message_select" \
$columns $message 2>/dev/null) || _exit_script
if ((items_count != 0)) && [[ -n "$selected_item" ]]; then
# Open the directory of the clicked item in the list.
_open_items_locations "$selected_item" "$resolve_links"
fi
elif _command_exists "kdialog"; then
columns=$(sed "s|--column=||g" <<<"$columns")
columns=$(tr ";" "\t" <<<"$columns")
message=$(tr "$FIELD_SEPARATOR" "\t" <<<"$message")
message="$columns"$'\n'$'\n'"$message"
kdialog --title "$(_get_script_name)" \
--geometry "${GUI_BOX_WIDTH}x${GUI_BOX_HEIGHT}" \
--textinputbox "" "$message" &>/dev/null || _exit_script
elif _command_exists "xmessage"; then
columns=$(sed "s|--column=||g" <<<"$columns")
columns=$(tr ";" "\t" <<<"$columns")
message=$(tr "$FIELD_SEPARATOR" "\t" <<<"$message")
message="$columns"$'\n'$'\n'"$message"
xmessage -title "$(_get_script_name)" \
"$message" &>/dev/null || _exit_script
fi
}
_display_password_box() {
# This function prompts the user to enter a password, either via the
# terminal or a graphical dialog box.
#
# Parameters:
# - $1 (message): A message to display as a prompt for the password.
local message=$1
local password=""
# Ask the user for the 'password'.
if ! _is_gui_session; then
read -r -p "$message " password >&2
elif _command_exists "zenity"; then
sleep 0.2 # Avoid 'wait_box' open before.
password=$(zenity \
--title="Password" --entry --hide-text --width="$GUI_INFO_WIDTH" \
--text "$message" 2>/dev/null) || return 1
elif _command_exists "kdialog"; then
sleep 0.2 # Avoid 'wait_box' open before.
password=$(kdialog --title "Password" \
--password "$message" 2>/dev/null) || return 1
fi
printf "%s" "$password"
}
_display_password_box_define() {
# This function prompts the user to enter a password and ensures the
# password is not empty.
local message="Type your password:"
local password=""
password=$(_display_password_box "$message") || return 1
# Check if the 'password' is not empty.
if [[ -z "$password" ]]; then
_display_error_box "The password can not be empty!"
return 1
fi
printf "%s" "$password"
}
_display_question_box() {
# This function prompts the user with a yes/no question and returns the
# user's response.
#
# Parameters:
# - $1 (message): The question message to display to the user.
local message=$1
local response=""
if ! _is_gui_session; then
read -r -p "$message [Y/n] " response
[[ ${response,,} == *"n"* ]] && return 1
elif _command_exists "zenity"; then
zenity --title "$(_get_script_name)" --question \
--width="$GUI_INFO_WIDTH" --text="$message" &>/dev/null || return 1
elif _command_exists "kdialog"; then
kdialog --title "$(_get_script_name)" \
--yesno "$message" &>/dev/null || return 1
elif _command_exists "xmessage"; then
xmessage -title "$(_get_script_name)" \
-buttons "Yes:0,No:1" "$message" &>/dev/null || return 1
fi
return 0
}
_display_text_box() {
# This function displays a message to the user in a text box, either in the
# terminal or using a GUI dialog.
#
# Parameters:
# - $1 (message): The message to display. If empty, a default message
# "(Empty result)" is shown.
local message=$1
_close_wait_box
_logs_consolidate ""
if [[ -z "$message" ]]; then
message="(Empty result)"
fi
if ! _is_gui_session; then
printf "%s\n" "$message"
elif _command_exists "zenity"; then
zenity --title "$(_get_script_name)" --text-info --no-wrap \
--width="$GUI_BOX_WIDTH" --height="$GUI_BOX_HEIGHT" \
<<<"$message" &>/dev/null || _exit_script
elif _command_exists "kdialog"; then
kdialog --title "$(_get_script_name)" \
--geometry "${GUI_BOX_WIDTH}x${GUI_BOX_HEIGHT}" \
--textinputbox "" \
"$message" &>/dev/null || _exit_script
elif _command_exists "xmessage"; then
xmessage -title "$(_get_script_name)" \
"$message" &>/dev/null || _exit_script
fi
}
_display_result_box() {
# This function displays a result summary at the end of a process,
# including error checking and output directory information.
#
# Parameters:
# - $1 (output_dir): The directory where output files are stored or
# expected to be.
local output_dir=$1
_close_wait_box
_logs_consolidate "$output_dir"
# If 'output_dir' parameter is defined.
if [[ -n "$output_dir" ]]; then
# Try to remove the output directory (if it is empty).
if [[ "$output_dir" == *"/$PREFIX_OUTPUT_DIR"* ]]; then
rmdir "$output_dir" &>/dev/null
fi
# Check if the output directory still exists.
if [[ -d "$output_dir" ]]; then
local dir_label=""
dir_label=$(_str_human_readable_path "$output_dir")
_display_info_box \
"Finished! The output files are in the $dir_label directory."
else
_display_info_box "Finished, but there is nothing to do."
fi
else
_display_info_box "Finished!"
fi
}
_display_wait_box() {
# This function displays a wait box to inform the user that a task is
# running and they need to wait.
#
# Parameters:
# - $1 (open_delay): Optional. The delay (in seconds) before the wait box
# is shown. Defaults to 2 seconds if not provided.
local open_delay=${1:-"2"}
local message="Running the task. Please, wait..."
_display_wait_box_message "$message" "$open_delay"
}
_display_wait_box_message() {
# This function displays a wait box (progress indicator) to inform the user
# that a task is in progress.
#
# Parameters:
# - $1 (message): The message to display inside the wait box (e.g.,
# "Running the task. Please, wait...").
# - $2 (open_delay): Optional. The delay (in seconds) before the wait box
# is shown. Defaults to 2 seconds if not provided.
local message=$1
local open_delay=${2:-"2"}
if ! _is_gui_session; then
# For non-GUI sessions, simply print the message to the console.
printf "%s\n" "$message" >&2
# Check if the Zenity is available.
elif _command_exists "zenity"; then
# Control flag to inform that a 'wait_box' will open
# (if the task takes over 2 seconds).
touch "$WAIT_BOX_CONTROL"
# Create the FIFO for communication with Zenity 'wait_box'.
if [[ ! -p "$WAIT_BOX_FIFO" ]]; then
mkfifo "$WAIT_BOX_FIFO"
fi
# Launch a background thread for Zenity 'wait_box':
# - Waits for the specified delay.
# - Opens the Zenity 'wait_box' if the control flag still exists.
# - If Zenity 'wait_box' fails or is cancelled, exit the script.
# shellcheck disable=SC2002
sleep "$open_delay" && [[ -f "$WAIT_BOX_CONTROL" ]] &&
tail -f -- "$WAIT_BOX_FIFO" | (zenity \
--title="$(_get_script_name)" --progress \
--width="$GUI_INFO_WIDTH" \
--pulsate --auto-close --text="$message" || _exit_script) &
# Check if the KDialog is available.
elif _command_exists "kdialog"; then
_get_qdbus_command || return 0
# Control flag to inform that a 'wait_box' will open
# (if the task takes over 2 seconds).
touch "$WAIT_BOX_CONTROL"
# Launch a background thread for KDialog 'wait_box':
# - Waits for the specified delay.
# - Opens the KDialog 'wait_box' if the control flag still exists.
sleep "$open_delay" && [[ -f "$WAIT_BOX_CONTROL" ]] &&
kdialog --title="$(_get_script_name)" \
--progressbar "$message" 0 >"$WAIT_BOX_CONTROL_KDE" &
# Launch another background thread to monitor the KDialog 'wait_box':
# - Periodically checks if the dialog has been closed or cancelled.
# - If KDialog 'wait_box' is cancelled, exit the script.
(
while [[ -f "$WAIT_BOX_CONTROL" ]] ||
[[ -f "$WAIT_BOX_CONTROL_KDE" ]]; do
if [[ -f "$WAIT_BOX_CONTROL_KDE" ]]; then
# Extract the D-Bus reference for the KDialog instance.
local dbus_ref=""
dbus_ref=$(cut -d " " -f 1 <"$WAIT_BOX_CONTROL_KDE")
if [[ -n "$dbus_ref" ]]; then
# Check if the user has cancelled the wait box.
$(_get_qdbus_command) "$dbus_ref" "/ProgressDialog" \
"wasCancelled" 2>/dev/null || _exit_script
fi
fi
sleep 0.2
done
) &
fi
}
_close_wait_box() {
# This function is responsible for closing any open "wait boxes" (progress
# indicators) that were displayed during the execution of a task. It checks
# for both Zenity and KDialog wait boxes and handles their closure.
# Check if 'wait_box' will open.
if [[ -f "$WAIT_BOX_CONTROL" ]]; then
rm -f -- "$WAIT_BOX_CONTROL" # Cancel the future open.
fi
# Check if Zenity 'wait_box' is open (waiting for an input in the FIFO).
if pgrep -fl "$WAIT_BOX_FIFO" &>/dev/null; then
# Close the Zenity using the FIFO.
printf "100\n" >"$WAIT_BOX_FIFO"
fi
# Check if KDialog 'wait_box' is open.
while [[ -f "$WAIT_BOX_CONTROL_KDE" ]]; do
# Extract the D-Bus reference for the KDialog instance.
local dbus_ref=""
dbus_ref=$(cut -d " " -f 1 <"$WAIT_BOX_CONTROL_KDE")
if [[ -n "$dbus_ref" ]]; then
# Close the KDialog 'wait_box'.
$(_get_qdbus_command) "$dbus_ref" "/ProgressDialog" \
"close" 2>/dev/null
rm -f -- "$WAIT_BOX_CONTROL_KDE"
fi
sleep 0.2
done
}
_exit_script() {
# This function is responsible for safely exiting the script by terminating
# all child processes associated with the current script and printing an
# exit message to the terminal.
_close_wait_box
local child_pids=""
local script_pid=$$
if ! _is_gui_session; then
printf "Exiting the script...\n" >&2
fi
# Get the process ID (PID) of all child processes.
child_pids=$(pstree -p "$script_pid" |
grep --only-matching --perl-regexp "\(+\K[^)]+")
# NOTE: Use 'xargs' and kill to send the SIGTERM signal to all child
# processes, including the current script.
# See the: https://www.baeldung.com/linux/safely-exit-scripts
xargs kill <<<"$child_pids" &>/dev/null
}
_find_filtered_files() {
# This function filters a list of files or directories based on various
# user-specified criteria, such as file type, extensions, and recursion.
#
# Parameters:
# - $1 (input_files): A space-separated string containing file or
# directory paths to filter. These paths are passed to the "find"
# command.
# - $2 (par_type): A string specifying the type of file to search for. It
# can be:
# - "file": To search for files and symbolic links.
# - "directory": To search for directories and symbolic links.
# - $3 (par_skip_extension): A string of file extensions to exclude from
# the search. Only files with extensions not matching this list will be
# included.
# - $4 (par_select_extension): A string of file extensions to include in
# the search. Only files with matching extensions will be included.
# - $5 (par_find_parameters): Optional. Additional parameters to be
# passed directly to the "find" command.
#
# Example:
# - Input: "dir1 dir2", "file", "", "txt|pdf", "true"
# - Output: A list of files with extensions ".txt" or ".pdf" from the
# directories "dir1" and "dir2", searched recursively.
local input_files=$1
local par_type=$2
local par_skip_extension=$3
local par_select_extension=$4
local par_find_parameters=$5
local filtered_files=""
local find_command=""
input_files=$(sed "s|'|'\"'\"'|g" <<<"$input_files")
input_files=$(sed "s|$FIELD_SEPARATOR|' '|g" <<<"$input_files")
# Build a 'find' command.
find_command="find '$input_files'"
if [[ -n "$par_find_parameters" ]]; then
find_command+=" $par_find_parameters"
fi
# Expand the directories with the 'find' command.
case "$par_type" in
"file") find_command+=" \( -type l -o -type f \)" ;;
"directory") find_command+=" \( -type l -o -type d \)" ;;
esac
if [[ -n "$par_select_extension" ]]; then
find_command+=" -regextype posix-extended "
find_command+=" -regex \".*\.($par_select_extension)$\""
fi
if [[ -n "$par_skip_extension" ]]; then
find_command+=" -regextype posix-extended "
find_command+=" ! -regex \".*\.($par_skip_extension)$\""
fi
find_command+=" ! -path \"$IGNORE_FIND_PATH\""
# shellcheck disable=SC2089
find_command+=" -print0"
# shellcheck disable=SC2086
filtered_files=$(eval $find_command 2>/dev/null |
tr "\0" "$FIELD_SEPARATOR")
filtered_files=$(_str_remove_empty_tokens "$filtered_files")
# Return the filtered files.
printf "%s" "$filtered_files"
}
_gdbus_notify() {
# This function sends a desktop notification using the "gdbus" tool, which
# interfaces with the D-Bus notification system (specifically the
# "org.freedesktop.Notifications" service).
#
# Parameters:
# - $1 (icon): The icon to display with the notification.
# - $2 (title): The title of the notification.
# - $3 (body): The main message to be displayed in the notification.
local icon=$1
local title=$2
local body=$3
local app_name=$title
local method="Notify"
local interface="org.freedesktop.Notifications"
local object_path="/org/freedesktop/Notifications"
# Use 'gdbus' to send the notification.
gdbus call --session --dest "$interface" --object-path "$object_path" \
--method "$interface.$method" "$app_name" 0 "$icon" "$title" "$body" \
"[]" '{"urgency": <1>}' 5000 &>/dev/null
}
_get_filename_dir() {
# This function extracts the directory path from a given file path.
#
# Parameters:
# - $1 (input_filename): The full path or relative path to the file.
local input_filename=$1
local dir=""
dir=$(cd -- "$(dirname -- "$input_filename")" &>/dev/null && pwd)
printf "%s" "$dir"
}
_get_filename_extension() {
# This function extracts the file extension from a given filename.
#
# Parameters:
# - $1 (filename): The input filename (can be absolute or relative).
local filename=$1
filename=$(sed -E "s|.*/(\.)*||g" <<<"$filename")
filename=$(sed -E "s|^(\.)*||g" <<<"$filename")
printf "%s" "$filename" |
grep --ignore-case --only-matching --perl-regexp \
"(\.tar)?\.[a-z0-9_~-]{0,15}$" || true
}
_get_filename_full_path() {
# This function returns the full absolute path of a given filename.
#
# Parameters:
# - $1 (input_filename): The input filename or relative path.
local input_filename=$1
local full_path=$input_filename
local dir=""
if [[ $input_filename != "/"* ]]; then
dir=$(_get_filename_dir "$input_filename")
full_path=$dir/$(basename -- "$input_filename")
fi
printf "%s" "$full_path"
}
_get_filename_next_suffix() {
# This function generates a unique filename by adding a numeric suffix to
# the base filename if a file with the same name already exists. It ensures
# that the new filename does not overwrite an existing file.
#
# Parameters:
# - $1 (filename): The input filename or path. This can be an absolute or
# relative filename. If the input file has an extension, it will be
# stripped for the purpose of generating the new filename.
local filename=$1
local filename_result=$filename
local filename_base=""
local filename_extension=""
# Directories do not have an extension.
if [[ -d "$filename" ]]; then
filename_base=$filename
else
filename_base=$(_strip_filename_extension "$filename")
filename_extension=$(_get_filename_extension "$filename")
fi
# Avoid overwriting a file. If there is a file with the same name,
# try to add a suffix, as 'file (1)', 'file (2)', ...
local count=1
while [[ -e "$filename_result" ]]; do
filename_result="$filename_base ($count)$filename_extension"
((count++))
done
printf "%s" "$filename_result"
}
_get_filenames_filemanager() {
# This function retrieves a list of selected filenames or URIs from a file
# manager (such as Caja, Nemo, or Nautilus) and processes the input
# accordingly. If no selection is detected, it falls back to using a
# standard input file list.
local input_files=""
# Try to use the information provided by the file manager.
if [[ -v "CAJA_SCRIPT_SELECTED_URIS" ]]; then
input_files=$CAJA_SCRIPT_SELECTED_URIS
elif [[ -v "NEMO_SCRIPT_SELECTED_URIS" ]]; then
input_files=$NEMO_SCRIPT_SELECTED_URIS
elif [[ -v "NAUTILUS_SCRIPT_SELECTED_URIS" ]]; then
input_files=$NAUTILUS_SCRIPT_SELECTED_URIS
fi
if [[ -n "$input_files" ]]; then
# Replace '\n' with '$FIELD_SEPARATOR'.
input_files=$(_convert_text_to_delimited_string "$input_files")
# Decode the URI list.
input_files=$(_text_uri_decode "$input_files")
else
input_files=$INPUT_FILES # Standard input.
input_files=$(_str_remove_empty_tokens "$input_files")
fi
printf "%s" "$input_files"
}
_get_files() {
# This function retrieves a list of files or directories based on the
# provided parameters and performs various filtering, validation, and
# sorting operations. The input files can be filtered by type, extension,
# mime type, etc. It also supports recursive directory expansion and
# validation of file conflicts.
#
# Parameters:
# - $1 (parameters): A string containing key-value pairs that configure