Keybindings and start of stream service
[PreCom.git] / menu.sh
1 #!/bin/bash
2
3 #TODO: There are probably instances where I should be quoting variables but am not.
4 #Basically, if there is any chance that an argument will have a space in it, it should
5 #be quoted when used.
6
7 INPUT=/tmp/menu.sh.$$
8 OUTPUT=/tmp/output.sh.$$
9
10 trap "rm $OUTPUT; rm $INPUT; exit" SIGHUP SIGINT SIGTERM
11
12 menu_json="$(./JSON.sh -l < menu.json)"
13 current_item="MainMenu"
14 backtitle="DivinElegy PreCom"
15 box_width=70
16 box_height=30
17 dialog_bin="/usr/bin/dialog"
18
19 while getopts ":d:" opt; do
20 case $opt in
21 d)
22 dialog_bin=$OPTARG
23 ;;
24 \?)
25 echo "Invalid option: -$OPTARG" >&2
26 exit 1
27 ;;
28 :)
29 echo "Option -$OPTARG requires an argument." >&2
30 exit 1
31 ;;
32 esac
33 done
34
35 function debug()
36 {
37 >&2 echo "$1"
38 sleep 2
39 }
40
41 function get_key_from_line()
42 {
43 key_re="(.+?)[[:space:]]+\""
44 while read -r line; do
45 [[ "$1" =~ $key_re ]] && echo ${BASH_REMATCH[1]} && break
46 done <<< "$menu_json"
47 }
48
49 #Pass something like MainMenu.type and it'll
50 #give the value
51 #nice bonus: If passed a full line from menu_json
52 #this will return the value for it
53 function get_value_from_key()
54 {
55 value_re="[[:space:]]+\"(.+)\"$"
56 while read -r line; do
57 [[ $line == ${1}* ]] && [[ $line =~ $value_re ]] && echo ${BASH_REMATCH[1]} && break
58 done <<< "$menu_json"
59 }
60
61 #Helper for short_path_to_full_path
62 #Given a menu item name, and a path to
63 #its parent (a real JSON path like MainMenu.items.0)
64 #this will return the path to the item from the given path
65 #For example if it is passed MainMenu.items.0 and "ITG" it might
66 #return: items.0
67 #name, path
68 function get_relative_child_path()
69 {
70 if [[ -z "$2" ]]; then
71 echo "$1"
72 else
73 re="${2//./\\.}\.items\.([0-9]+?)\.name"
74 while read -r line; do
75 if [[ "$line" =~ $re ]]; then
76 sub_path=${BASH_REMATCH[1]}
77 key=$(get_key_from_line "$line")
78 value=$(get_value_from_key "$key")
79 [[ "$value" == "$1" ]] && echo "items.$sub_path"
80 fi
81 done <<< "$menu_json"
82 fi
83 }
84
85 #When talking about menus it's useful to
86 #be able to say something like:
87 #MainMenu.Service.ITG instead of
88 #MainMenu.items.0.items.0
89 #This function converts the nice path
90 #to the real JSON path.
91 function short_path_to_full_path()
92 {
93 path_so_far=""
94 while IFS='.' read -ra parts; do
95 for i in "${parts[@]}"; do
96 relative_path=$(get_relative_child_path "$i" "$path_so_far")
97
98 if [[ -z "$path_so_far" ]]; then
99 path_so_far="${relative_path}"
100 else
101 path_so_far="${path_so_far}.${relative_path}"
102 fi
103 done
104 done <<< "$1"
105
106 echo "$path_so_far"
107 }
108
109
110 #Functions below here are for convenience.
111 #They allow extraction of information about
112 #menus without having to constantly specifiy
113 #Menu.items.Thing.items.MenuIWant
114 #Instead you can just do Menu.Thing.MenuIWant
115
116 #Use a dot delim path to the item
117 #returns the type of the item
118 #e.g.,
119 # menu
120 # service
121 function get_item_type()
122 {
123 full_path=$(short_path_to_full_path "$1")
124 get_value_from_key "$full_path.type"
125 }
126
127 function get_item_description()
128 {
129 full_path=$(short_path_to_full_path "$1")
130 get_value_from_key "$full_path.description"
131 }
132
133 #(dot-delim, key)
134 function get_item_key()
135 {
136 full_path=$(short_path_to_full_path $1)
137 get_value_from_key "$full_path.$2"
138 }
139
140 #Returns true if line is a child
141 #of the dot-delim menu passed in
142 #(line, dot-delim)
143 #note: child means _direct_ child
144 #this will exclude grandchildren etc
145 function is_item_of_menu()
146 {
147 full_path=$(short_path_to_full_path "$2")
148
149 items_in_path=$(grep -o items <<< "$full_path" | wc -l)
150 items_in_line=$(grep -o items <<< "$1" | wc -l)
151
152 if [[ $1 == ${full_path}* ]] && [[ $((items_in_path + 1)) == $items_in_line ]]; then
153 return 0
154 else
155 return 1
156 fi
157 }
158
159 function is_child_of()
160 {
161 if [[ $1 == ${2}* ]]; then
162 return 0
163 else
164 return 1
165 fi
166 }
167
168 #file, #detail
169 function get_adapter_detail()
170 {
171 value_re="# ${2}:[[:space:]]+(.+)$"
172
173 while read -r line; do
174 [[ $line =~ $value_re ]] && echo ${BASH_REMATCH[1]} && break
175 done < "$1"
176 }
177
178 #Use a dot delimited path to the menu
179 #e.g.,
180 # MainMenu.Services
181 # MainMenu.Services.Streaming
182 function render_menu()
183 {
184 options=()
185 while read -r line; do
186 #Cracks the shits without the quotes on the args,
187 #I don't know why.
188 #Yes I do, it's because $line has a tab followed by text in it.
189 #Without the quotes it thinks what follows the tab is the second arg
190 if is_item_of_menu "$line" "$1"; then
191 #Match on name as it is unique per item
192 #Multiple lines will match per item without this
193 name_re=".name[[:space:]].+?\"(.+)\""
194 if [[ $line =~ $name_re ]]; then
195 key=$(get_key_from_line "$line")
196 desc=$(get_value_from_key "${key%.*}.description")
197 name=$(get_value_from_key "${key%.*}.name")
198 options+=("$name" "$desc")
199 fi
200 fi
201 done <<< "$menu_json"
202
203 if [[ $1 == "MainMenu" ]]; then
204 options+=("Quit" "Exit the menu masterqueef")
205 else
206 options+=("Back" "Go back")
207 fi
208
209 #Also cracks the shits without quotes.
210 #Also don't know why.
211 $dialog_bin --clear --backtitle "$backtitle" --title "${1//./>}" --nocancel --menu "$(get_item_description $1)" "$box_height" "$box_width" 4 "${options[@]}" 2>"${INPUT}"
212 }
213
214 function toggle_service()
215 {
216 service_command=$(get_item_key $1 command)
217 service_name=$(basename $service_command)
218
219 #If the quotes aren't here then the application launches
220 #Not sure why
221 pid="$(pgrep $service_name)"
222
223 #XXX: Eww. Too many ifs. Clean this?
224 if [[ $pid ]]; then
225 if [[ "$2" != "skip_confirmation_dialogs" ]]; then
226 kill -9 $pid
227 else
228 $dialog_bin --clear --backtitle "$backtitle" --title "Disable $service_name" --yesno "This will stop $service_name. Are you sure?" 6 50
229 [[ $? == 0 ]] && kill -9 $pid
230 fi
231 else
232 if [[ "$2" == "skip_confirmation_dialogs" ]]; then
233 $service_command > /dev/null 2>&1 &
234 else
235 $dialog_bin --clear --backtitle "$backtitle" --title "Enable $service_name" --yesno "This will start $service_name. Are you sure?" 6 50
236 [[ $? == 0 ]] && $service_command > /dev/null 2>&1 &
237 fi
238 fi
239
240 #Kind of a hack? When we get here current_item will be:
241 #thing.otherThing.this_service
242 #but after we leave here we want to render thing.otherThing
243 #so returning Back gets the main loop to do that.
244 echo "Back" > "${INPUT}"
245 }
246
247 function run_task()
248 {
249 #I tried $(short_path_to_full_path $1).commands
250 #but it produced a weird result.
251 local full_path=$(short_path_to_full_path "$1")
252
253 while read -r line; do
254 if is_child_of "$line" "${full_path}.commands"; then
255 command_re=".\/(.+.sh)"
256 if [[ $line =~ $command_re ]]; then
257 widget=$(get_adapter_detail "${BASH_REMATCH[1]}" "widget")
258 key=$(get_key_from_line "$line")
259 command="$(get_value_from_key $key)"
260 title=$(get_value_from_key "${key%.*}.title")
261
262 case "$widget" in
263 gauge) eval "$command" | $dialog_bin --clear --backtitle "$backtitle" --title "Running $1" --gauge "$title" 6 60;;
264 msgbox) [[ $2 != "skip_confirmation_dialogs" ]] && eval "$command" | $dialog_bin --clear --backtitle "$backtitle" --title "$title" --msgbox "$(eval \"$command\")" 8 40;;
265 esac
266 fi
267 fi
268 done <<< "$menu_json"
269
270 echo "Back" > "${INPUT}"
271 }
272
273 function run_preset()
274 {
275 local full_path=$(short_path_to_full_path "$1")
276 while read -r line; do
277 if is_child_of "$line" "${full_path}.itemsToRun"; then
278 key=$(get_key_from_line "$line")
279 item=$(get_value_from_key "$key")
280 process_item "$item" "skip_confirmation_dialogs"
281 fi
282 done <<< "$menu_json"
283
284 echo "Back" > "${INPUT}"
285 }
286
287 function process_item()
288 {
289 type=$(get_item_type "$1")
290 case $type in
291 menu) render_menu "$1" "$2";;
292 service) toggle_service "$1" "$2";;
293 task) run_task "$1" "$2";;
294 preset) run_preset "$1" "$2";;
295 esac
296 }
297
298 #############
299 # Main loop #
300 ############
301
302 while true; do
303 process_item "$current_item"
304 #todo: Is it possible to avoid using a file for this?
305 selection=$(<"${INPUT}")
306
307 case $selection in
308 Quit) break;;
309 Back) current_item="${current_item%.*}";;
310 *) current_item="$current_item.$selection";;
311 esac
312 done
313
314 ###########
315 # Cleanup #
316 ###########
317 clear
318
319 [[ -f $OUTPUT ]] && rm $OUTPUT
320 [[ -f $INPUT ]] && rm $INPUT