1 /*
2 Copyright (c) 2008-2018
3 	Lars-Dominik Braun <lars@6xq.net>
4 
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11 
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14 
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22 */
23 
24 /* functions responding to user's keystrokes */
25 
26 #include "config.h"
27 
28 #include <string.h>
29 #include <unistd.h>
30 #include <pthread.h>
31 #include <assert.h>
32 #include <string.h>
33 
34 #include "ui.h"
35 #include "ui_readline.h"
36 #include "ui_dispatch.h"
37 
38 /*	standard eventcmd call
39  */
40 #define BarUiActDefaultEventcmd(name) BarUiStartEventCmd (&app->settings, \
41 		name, selStation, selSong, &app->player, app->ph.stations, \
42 		pRet, wRet)
43 
44 /*	standard piano call
45  */
46 #define BarUiActDefaultPianoCall(call, arg) BarUiPianoCall (app, \
47 		call, arg, &pRet, &wRet)
48 
49 /*	helper to _really_ skip a song (unlock mutex, quit player)
50  *	@param player handle
51  */
BarUiDoSkipSong(player_t * const player)52 static inline void BarUiDoSkipSong (player_t * const player) {
53 	assert (player != NULL);
54 
55 	pthread_mutex_lock (&player->lock);
56 	player->doQuit = true;
57 	player->doPause = false;
58 	pthread_cond_broadcast (&player->cond);
59 	pthread_mutex_unlock (&player->lock);
60 	pthread_mutex_lock (&player->aoplayLock);
61 	pthread_cond_broadcast (&player->aoplayCond);
62 	pthread_mutex_unlock (&player->aoplayLock);
63 }
64 
65 /*	transform station if necessary to allow changes like rename, rate, ...
66  *	@param piano handle
67  *	@param transform this station
68  *	@return 0 = error, 1 = everything went well
69  */
BarTransformIfShared(BarApp_t * app,PianoStation_t * station)70 static int BarTransformIfShared (BarApp_t *app, PianoStation_t *station) {
71 	PianoReturn_t pRet;
72 	CURLcode wRet;
73 
74 	assert (station != NULL);
75 
76 	/* shared stations must be transformed */
77 	if (!station->isCreator) {
78 		BarUiMsg (&app->settings, MSG_INFO, "Transforming station... ");
79 		if (!BarUiPianoCall (app, PIANO_REQUEST_TRANSFORM_STATION, station,
80 				&pRet, &wRet)) {
81 			return 0;
82 		}
83 	}
84 	return 1;
85 }
86 
87 /*	print current shortcut configuration
88  */
BarUiActCallback(BarUiActHelp)89 BarUiActCallback(BarUiActHelp) {
90 	BarUiMsg (&app->settings, MSG_NONE, "\r");
91 	for (size_t i = 0; i < BAR_KS_COUNT; i++) {
92 		if (dispatchActions[i].helpText != NULL &&
93 				(context & dispatchActions[i].context) == dispatchActions[i].context &&
94 				app->settings.keys[i] != BAR_KS_DISABLED) {
95 			BarUiMsg (&app->settings, MSG_LIST, "%c    %s\n", app->settings.keys[i],
96 					dispatchActions[i].helpText);
97 		}
98 	}
99 }
100 
101 /*	add more music to current station
102  */
BarUiActCallback(BarUiActAddMusic)103 BarUiActCallback(BarUiActAddMusic) {
104 	PianoReturn_t pRet;
105 	CURLcode wRet;
106 	PianoRequestDataAddSeed_t reqData;
107 
108 	assert (selStation != NULL);
109 
110 	reqData.musicId = BarUiSelectMusicId (app, selStation,
111 			"Add artist or title to station: ");
112 	if (reqData.musicId != NULL) {
113 		if (!BarTransformIfShared (app, selStation)) {
114 			return;
115 		}
116 		reqData.station = selStation;
117 
118 		BarUiMsg (&app->settings, MSG_INFO, "Adding music to station... ");
119 		BarUiActDefaultPianoCall (PIANO_REQUEST_ADD_SEED, &reqData);
120 
121 		free (reqData.musicId);
122 
123 		BarUiActDefaultEventcmd ("stationaddmusic");
124 	}
125 }
126 
127 /*	ban song
128  */
BarUiActCallback(BarUiActBanSong)129 BarUiActCallback(BarUiActBanSong) {
130 	PianoReturn_t pRet;
131 	CURLcode wRet;
132 	PianoStation_t *realStation;
133 
134 	assert (selStation != NULL);
135 	assert (selSong != NULL);
136 	assert (selSong->stationId != NULL);
137 
138 	if ((realStation = PianoFindStationById (app->ph.stations,
139 			selSong->stationId)) == NULL) {
140 		assert (0);
141 		return;
142 	}
143 	if (!BarTransformIfShared (app, realStation)) {
144 		return;
145 	}
146 
147 	PianoRequestDataRateSong_t reqData;
148 	reqData.song = selSong;
149 	reqData.rating = PIANO_RATE_BAN;
150 
151 	BarUiMsg (&app->settings, MSG_INFO, "Banning song... ");
152 	if (BarUiActDefaultPianoCall (PIANO_REQUEST_RATE_SONG, &reqData) &&
153 			selSong == app->playlist) {
154 		BarUiDoSkipSong (&app->player);
155 	}
156 	BarUiActDefaultEventcmd ("songban");
157 }
158 
159 /*	create new station
160  */
BarUiActCallback(BarUiActCreateStation)161 BarUiActCallback(BarUiActCreateStation) {
162 	PianoReturn_t pRet;
163 	CURLcode wRet;
164 	PianoRequestDataCreateStation_t reqData;
165 
166 	reqData.type = PIANO_MUSICTYPE_INVALID;
167 	reqData.token = BarUiSelectMusicId (app, NULL,
168 			"Create station from artist or title: ");
169 	if (reqData.token != NULL) {
170 		BarUiMsg (&app->settings, MSG_INFO, "Creating station... ");
171 		BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
172 		free (reqData.token);
173 		BarUiActDefaultEventcmd ("stationcreate");
174 	}
175 }
176 
177 /*	create new station
178  */
BarUiActCallback(BarUiActCreateStationFromSong)179 BarUiActCallback(BarUiActCreateStationFromSong) {
180 	PianoReturn_t pRet;
181 	CURLcode wRet;
182 	PianoRequestDataCreateStation_t reqData;
183 	char selectBuf[2];
184 
185 	reqData.token = selSong->trackToken;
186 	reqData.type = PIANO_MUSICTYPE_INVALID;
187 
188 	BarUiMsg (&app->settings, MSG_QUESTION, "Create station from [s]ong or [a]rtist? ");
189 	BarReadline (selectBuf, sizeof (selectBuf), "sa", &app->input,
190 			BAR_RL_FULLRETURN, -1);
191 	switch (selectBuf[0]) {
192 		case 's':
193 			reqData.type = PIANO_MUSICTYPE_SONG;
194 			break;
195 
196 		case 'a':
197 			reqData.type = PIANO_MUSICTYPE_ARTIST;
198 			break;
199 	}
200 	if (reqData.type != PIANO_MUSICTYPE_INVALID) {
201 		BarUiMsg (&app->settings, MSG_INFO, "Creating station... ");
202 		BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
203 		BarUiActDefaultEventcmd ("stationcreate");
204 	}
205 }
206 
207 /*	add shared station by id
208  */
BarUiActCallback(BarUiActAddSharedStation)209 BarUiActCallback(BarUiActAddSharedStation) {
210 	PianoReturn_t pRet;
211 	CURLcode wRet;
212 	char stationId[50];
213 	PianoRequestDataCreateStation_t reqData;
214 
215 	reqData.token = stationId;
216 	reqData.type = PIANO_MUSICTYPE_INVALID;
217 
218 	BarUiMsg (&app->settings, MSG_QUESTION, "Station id: ");
219 	if (BarReadline (stationId, sizeof (stationId), "0123456789", &app->input,
220 			BAR_RL_DEFAULT, -1) > 0) {
221 		BarUiMsg (&app->settings, MSG_INFO, "Adding shared station... ");
222 		BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
223 		BarUiActDefaultEventcmd ("stationaddshared");
224 	}
225 }
226 
drainPlaylist(BarApp_t * const app)227 static void drainPlaylist (BarApp_t * const app) {
228 	BarUiDoSkipSong (&app->player);
229 	if (app->playlist != NULL) {
230 		/* drain playlist */
231 		PianoDestroyPlaylist (PianoListNextP (app->playlist));
232 		app->playlist->head.next = NULL;
233 	}
234 }
235 
236 /*	delete current station
237  */
BarUiActCallback(BarUiActDeleteStation)238 BarUiActCallback(BarUiActDeleteStation) {
239 	PianoReturn_t pRet;
240 	CURLcode wRet;
241 
242 	assert (selStation != NULL);
243 
244 	BarUiMsg (&app->settings, MSG_QUESTION, "Really delete \"%s\"? [yN] ",
245 			selStation->name);
246 	if (BarReadlineYesNo (false, &app->input)) {
247 		BarUiMsg (&app->settings, MSG_INFO, "Deleting station... ");
248 		if (BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_STATION,
249 				selStation) && selStation == app->curStation) {
250 			drainPlaylist (app);
251 			app->nextStation = NULL;
252 			/* XXX: usually we shoudn’t touch cur*, but DELETE_STATION destroys
253 			 * station struct */
254 			app->curStation = NULL;
255 			selStation = NULL;
256 		}
257 		BarUiActDefaultEventcmd ("stationdelete");
258 	}
259 }
260 
261 /*	explain pandora's song choice
262  */
BarUiActCallback(BarUiActExplain)263 BarUiActCallback(BarUiActExplain) {
264 	PianoReturn_t pRet;
265 	CURLcode wRet;
266 	PianoRequestDataExplain_t reqData;
267 
268 	assert (selSong != NULL);
269 
270 	reqData.song = selSong;
271 
272 	BarUiMsg (&app->settings, MSG_INFO, "Receiving explanation... ");
273 	if (BarUiActDefaultPianoCall (PIANO_REQUEST_EXPLAIN, &reqData)) {
274 		BarUiMsg (&app->settings, MSG_INFO, "%s\n", reqData.retExplain);
275 		free (reqData.retExplain);
276 	}
277 	BarUiActDefaultEventcmd ("songexplain");
278 }
279 
280 /*	choose genre station and add it as shared station
281  */
BarUiActCallback(BarUiActStationFromGenre)282 BarUiActCallback(BarUiActStationFromGenre) {
283 	PianoReturn_t pRet;
284 	CURLcode wRet;
285 	const PianoGenreCategory_t *curCat;
286 	const PianoGenre_t *curGenre;
287 	int i;
288 
289 	/* receive genre stations list if not yet available */
290 	if (app->ph.genreStations == NULL) {
291 		BarUiMsg (&app->settings, MSG_INFO, "Receiving genre stations... ");
292 		const bool ret = BarUiActDefaultPianoCall (
293 				PIANO_REQUEST_GET_GENRE_STATIONS, NULL);
294 		BarUiActDefaultEventcmd ("stationfetchgenre");
295 		if (!ret) {
296 			return;
297 		}
298 	}
299 
300 	/* print all available categories */
301 	curCat = app->ph.genreStations;
302 	i = 0;
303 	PianoListForeachP (curCat) {
304 		BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curCat->name);
305 		i++;
306 	}
307 
308 	do {
309 		/* select category or exit */
310 		BarUiMsg (&app->settings, MSG_QUESTION, "Select category: ");
311 		if (BarReadlineInt (&i, &app->input) == 0) {
312 			return;
313 		}
314 		curCat = PianoListGetP (app->ph.genreStations, i);
315 	} while (curCat == NULL);
316 
317 	/* print all available stations */
318 	i = 0;
319 	curGenre = curCat->genres;
320 	PianoListForeachP (curGenre) {
321 		BarUiMsg (&app->settings, MSG_LIST, "%2i) %s\n", i, curGenre->name);
322 		i++;
323 	}
324 
325 	do {
326 		BarUiMsg (&app->settings, MSG_QUESTION, "Select genre: ");
327 		if (BarReadlineInt (&i, &app->input) == 0) {
328 			return;
329 		}
330 		curGenre = PianoListGetP (curCat->genres, i);
331 	} while (curGenre == NULL);
332 
333 	/* create station */
334 	PianoRequestDataCreateStation_t reqData;
335 	reqData.token = curGenre->musicId;
336 	reqData.type = PIANO_MUSICTYPE_INVALID;
337 	BarUiMsg (&app->settings, MSG_INFO, "Adding genre station \"%s\"... ",
338 			curGenre->name);
339 	BarUiActDefaultPianoCall (PIANO_REQUEST_CREATE_STATION, &reqData);
340 	BarUiActDefaultEventcmd ("stationaddgenre");
341 }
342 
343 /*	print verbose song information
344  */
BarUiActCallback(BarUiActSongInfo)345 BarUiActCallback(BarUiActSongInfo) {
346 	assert (selStation != NULL);
347 	assert (selSong != NULL);
348 
349 	BarUiPrintStation (&app->settings, selStation);
350 	/* print real station if quickmix */
351 	BarUiPrintSong (&app->settings, selSong,
352 			selStation->isQuickMix ?
353 			PianoFindStationById (app->ph.stations, selSong->stationId) :
354 			NULL);
355 }
356 
357 /*	print some debugging information
358  */
BarUiActCallback(BarUiActDebug)359 BarUiActCallback(BarUiActDebug) {
360 	assert (selSong != NULL);
361 
362 	/* print debug-alike infos */
363 	BarUiMsg (&app->settings, MSG_NONE,
364 			"album:\t%s\n"
365 			"artist:\t%s\n"
366 			"audioFormat:\t%i\n"
367 			"audioUrl:\t%s\n"
368 			"coverArt:\t%s\n"
369 			"detailUrl:\t%s\n"
370 			"fileGain:\t%f\n"
371 			"musicId:\t%s\n"
372 			"rating:\t%i\n"
373 			"stationId:\t%s\n"
374 			"title:\t%s\n"
375 			"trackToken:\t%s\n",
376 			selSong->album,
377 			selSong->artist,
378 			selSong->audioFormat,
379 			selSong->audioUrl,
380 			selSong->coverArt,
381 			selSong->detailUrl,
382 			selSong->fileGain,
383 			selSong->musicId,
384 			selSong->rating,
385 			selSong->stationId,
386 			selSong->title,
387 			selSong->trackToken);
388 }
389 
390 /*	rate current song
391  */
BarUiActCallback(BarUiActLoveSong)392 BarUiActCallback(BarUiActLoveSong) {
393 	PianoReturn_t pRet;
394 	CURLcode wRet;
395 	PianoStation_t *realStation;
396 
397 	assert (selStation != NULL);
398 	assert (selSong != NULL);
399 	assert (selSong->stationId != NULL);
400 
401 	if ((realStation = PianoFindStationById (app->ph.stations,
402 			selSong->stationId)) == NULL) {
403 		assert (0);
404 		return;
405 	}
406 	if (!BarTransformIfShared (app, realStation)) {
407 		return;
408 	}
409 
410 	PianoRequestDataRateSong_t reqData;
411 	reqData.song = selSong;
412 	reqData.rating = PIANO_RATE_LOVE;
413 
414 	BarUiMsg (&app->settings, MSG_INFO, "Loving song... ");
415 	BarUiActDefaultPianoCall (PIANO_REQUEST_RATE_SONG, &reqData);
416 	BarUiActDefaultEventcmd ("songlove");
417 }
418 
419 /*	skip song
420  */
BarUiActCallback(BarUiActSkipSong)421 BarUiActCallback(BarUiActSkipSong) {
422 	BarUiDoSkipSong (&app->player);
423 }
424 
425 /*	play
426  */
BarUiActCallback(BarUiActPlay)427 BarUiActCallback(BarUiActPlay) {
428 	pthread_mutex_lock (&app->player.lock);
429 	app->player.doPause = false;
430 	pthread_cond_broadcast (&app->player.cond);
431 	pthread_mutex_unlock (&app->player.lock);
432 }
433 
434 /*	pause
435  */
BarUiActCallback(BarUiActPause)436 BarUiActCallback(BarUiActPause) {
437 	pthread_mutex_lock (&app->player.lock);
438 	app->player.doPause = true;
439 	pthread_cond_broadcast (&app->player.cond);
440 	pthread_mutex_unlock (&app->player.lock);
441 }
442 
443 /*	toggle pause
444  */
BarUiActCallback(BarUiActTogglePause)445 BarUiActCallback(BarUiActTogglePause) {
446 	pthread_mutex_lock (&app->player.lock);
447 	app->player.doPause = !app->player.doPause;
448 	pthread_cond_broadcast (&app->player.cond);
449 	pthread_mutex_unlock (&app->player.lock);
450 }
451 
452 /*	rename current station
453  */
BarUiActCallback(BarUiActRenameStation)454 BarUiActCallback(BarUiActRenameStation) {
455 	PianoReturn_t pRet;
456 	CURLcode wRet;
457 	char lineBuf[100];
458 
459 	assert (selStation != NULL);
460 
461 	BarUiMsg (&app->settings, MSG_QUESTION, "New name: ");
462 	if (BarReadlineStr (lineBuf, sizeof (lineBuf), &app->input, BAR_RL_DEFAULT) > 0) {
463 		PianoRequestDataRenameStation_t reqData;
464 		if (!BarTransformIfShared (app, selStation)) {
465 			return;
466 		}
467 
468 		reqData.station = selStation;
469 		reqData.newName = lineBuf;
470 
471 		BarUiMsg (&app->settings, MSG_INFO, "Renaming station... ");
472 		BarUiActDefaultPianoCall (PIANO_REQUEST_RENAME_STATION, &reqData);
473 		BarUiActDefaultEventcmd ("stationrename");
474 	}
475 }
476 
477 /*	play another station
478  */
BarUiActCallback(BarUiActSelectStation)479 BarUiActCallback(BarUiActSelectStation) {
480 	PianoStation_t *newStation = BarUiSelectStation (app, app->ph.stations,
481 			"Select station: ", NULL, app->settings.autoselect);
482 	if (newStation != NULL) {
483 		app->nextStation = newStation;
484 		drainPlaylist (app);
485 	}
486 }
487 
488 /*	ban song for 1 month
489  */
BarUiActCallback(BarUiActTempBanSong)490 BarUiActCallback(BarUiActTempBanSong) {
491 	PianoReturn_t pRet;
492 	CURLcode wRet;
493 
494 	assert (selSong != NULL);
495 
496 	BarUiMsg (&app->settings, MSG_INFO, "Putting song on shelf... ");
497 	if (BarUiActDefaultPianoCall (PIANO_REQUEST_ADD_TIRED_SONG, selSong) &&
498 			selSong == app->playlist) {
499 		BarUiDoSkipSong (&app->player);
500 	}
501 	BarUiActDefaultEventcmd ("songshelf");
502 }
503 
504 /*	print upcoming songs
505  */
BarUiActCallback(BarUiActPrintUpcoming)506 BarUiActCallback(BarUiActPrintUpcoming) {
507 	PianoSong_t * const nextSong = PianoListNextP (selSong);
508 	if (nextSong != NULL) {
509 		BarUiListSongs (app, nextSong, NULL);
510 	} else {
511 		BarUiMsg (&app->settings, MSG_INFO, "No songs in queue.\n");
512 	}
513 }
514 
515 /*	selectStation callback used by BarUiActSelectQuickMix; toggle, select
516  *	all/none
517  */
BarUiActQuickmixCallback(BarApp_t * app,char * buf)518 static void BarUiActQuickmixCallback (BarApp_t *app, char *buf) {
519 	PianoStation_t *curStation = app->ph.stations;
520 
521 	/* do nothing if buf is empty/contains more than one character */
522 	if (buf[0] == '\0' || buf[1] != '\0') {
523 		return;
524 	}
525 
526 	switch (*buf) {
527 		case 't':
528 			/* toggle */
529 			PianoListForeachP (curStation) {
530 				curStation->useQuickMix = !curStation->useQuickMix;
531 			}
532 			*buf = '\0';
533 			break;
534 
535 		case 'a':
536 			/* enable all */
537 			PianoListForeachP (curStation) {
538 				curStation->useQuickMix = true;
539 			}
540 			*buf = '\0';
541 			break;
542 
543 		case 'n':
544 			/* enable none */
545 			PianoListForeachP (curStation) {
546 				curStation->useQuickMix = false;
547 			}
548 			*buf = '\0';
549 			break;
550 	}
551 }
552 
553 /*	if current station is a quickmix: select stations that are played in
554  *	quickmix
555  */
BarUiActCallback(BarUiActSelectQuickMix)556 BarUiActCallback(BarUiActSelectQuickMix) {
557 	PianoReturn_t pRet;
558 	CURLcode wRet;
559 
560 	assert (selStation != NULL);
561 
562 	if (selStation->isQuickMix) {
563 		PianoStation_t *toggleStation;
564 		while ((toggleStation = BarUiSelectStation (app, app->ph.stations,
565 				"Toggle QuickMix for station: ",
566 				BarUiActQuickmixCallback, false)) != NULL) {
567 			toggleStation->useQuickMix = !toggleStation->useQuickMix;
568 		}
569 		BarUiMsg (&app->settings, MSG_INFO, "Setting QuickMix stations... ");
570 		BarUiActDefaultPianoCall (PIANO_REQUEST_SET_QUICKMIX, NULL);
571 		BarUiActDefaultEventcmd ("stationquickmixtoggle");
572 	} else {
573 		BarUiMsg (&app->settings, MSG_ERR, "Please select a QuickMix station first.\n");
574 	}
575 }
576 
577 /*	quit
578  */
BarUiActCallback(BarUiActQuit)579 BarUiActCallback(BarUiActQuit) {
580 	app->doQuit = true;
581 	BarUiDoSkipSong (&app->player);
582 }
583 
584 /*	song history
585  */
BarUiActCallback(BarUiActHistory)586 BarUiActCallback(BarUiActHistory) {
587 	char buf[2];
588 	PianoSong_t *histSong;
589 
590 	if (app->songHistory != NULL) {
591 		histSong = BarUiSelectSong (app, app->songHistory,
592 				&app->input);
593 		if (histSong != NULL) {
594 			BarKeyShortcutId_t action;
595 			PianoStation_t *songStation = PianoFindStationById (app->ph.stations,
596 					histSong->stationId);
597 
598 			if (songStation == NULL) {
599 				BarUiMsg (&app->settings, MSG_ERR, "Station does not exist any more.\n");
600 				return;
601 			}
602 
603 			do {
604 				action = BAR_KS_COUNT;
605 
606 				BarUiMsg (&app->settings, MSG_QUESTION, "What to do with this song? ");
607 
608 				if (BarReadline (buf, sizeof (buf), NULL, &app->input,
609 						BAR_RL_FULLRETURN, -1) > 0) {
610 					/* actions assume that selStation is the song's original
611 					 * station */
612 					action = BarUiDispatch (app, buf[0], songStation, histSong,
613 							false, BAR_DC_UNDEFINED);
614 				}
615 			} while (action == BAR_KS_HELP);
616 		} /* end if histSong != NULL */
617 	} else {
618 		BarUiMsg (&app->settings, MSG_INFO, (app->settings.history == 0) ? "History disabled.\n" :
619 				"No history yet.\n");
620 	}
621 }
622 
623 /*	create song bookmark
624  */
BarUiActCallback(BarUiActBookmark)625 BarUiActCallback(BarUiActBookmark) {
626 	PianoReturn_t pRet;
627 	CURLcode wRet;
628 	char selectBuf[2];
629 
630 	assert (selSong != NULL);
631 
632 	BarUiMsg (&app->settings, MSG_QUESTION, "Bookmark [s]ong or [a]rtist? ");
633 	BarReadline (selectBuf, sizeof (selectBuf), "sa", &app->input,
634 			BAR_RL_FULLRETURN, -1);
635 	if (selectBuf[0] == 's') {
636 		BarUiMsg (&app->settings, MSG_INFO, "Bookmarking song... ");
637 		BarUiActDefaultPianoCall (PIANO_REQUEST_BOOKMARK_SONG, selSong);
638 		BarUiActDefaultEventcmd ("songbookmark");
639 	} else if (selectBuf[0] == 'a') {
640 		BarUiMsg (&app->settings, MSG_INFO, "Bookmarking artist... ");
641 		BarUiActDefaultPianoCall (PIANO_REQUEST_BOOKMARK_ARTIST, selSong);
642 		BarUiActDefaultEventcmd ("artistbookmark");
643 	}
644 }
645 
646 /*	decrease volume
647  */
BarUiActCallback(BarUiActVolDown)648 BarUiActCallback(BarUiActVolDown) {
649 	--app->settings.volume;
650 	BarPlayerSetVolume (&app->player);
651 }
652 
653 /*	increase volume
654  */
BarUiActCallback(BarUiActVolUp)655 BarUiActCallback(BarUiActVolUp) {
656 	++app->settings.volume;
657 	BarPlayerSetVolume (&app->player);
658 }
659 
660 /*	reset volume
661  */
BarUiActCallback(BarUiActVolReset)662 BarUiActCallback(BarUiActVolReset) {
663 	app->settings.volume = 0;
664 	BarPlayerSetVolume (&app->player);
665 }
666 
boolToYesNo(const bool value)667 static const char *boolToYesNo (const bool value) {
668 	return value ? "yes" : "no";
669 }
670 
671 /*	change pandora settings
672  */
BarUiActCallback(BarUiActSettings)673 BarUiActCallback(BarUiActSettings) {
674 	PianoReturn_t pRet;
675 	CURLcode wRet;
676 	PianoSettings_t settings;
677 	PianoRequestDataChangeSettings_t reqData;
678 	bool modified = false;
679 
680 	memset (&settings, 0, sizeof (settings));
681 	memset (&reqData, 0, sizeof (reqData));
682 
683 	BarUiMsg (&app->settings, MSG_INFO, "Retrieving settings... ");
684 	bool bret = BarUiActDefaultPianoCall (PIANO_REQUEST_GET_SETTINGS, &settings);
685 	BarUiActDefaultEventcmd ("settingsget");
686 	if (!bret) {
687 		return;
688 	}
689 
690 	BarUiMsg (&app->settings, MSG_LIST, " 0) Username (%s)\n", settings.username);
691 	BarUiMsg (&app->settings, MSG_LIST, " 1) Password (*****)\n");
692 	BarUiMsg (&app->settings, MSG_LIST, " 2) Explicit content filter (%s)\n",
693 			boolToYesNo (settings.explicitContentFilter));
694 
695 	while (true) {
696 		int val;
697 
698 		BarUiMsg (&app->settings, MSG_QUESTION, "Change setting: ");
699 		if (BarReadlineInt (&val, &app->input) == 0) {
700 			break;
701 		}
702 
703 		switch (val) {
704 			case 0: {
705 				/* username */
706 				char buf[80];
707 				BarUiMsg (&app->settings, MSG_QUESTION, "New username: ");
708 				if (BarReadlineStr (buf, sizeof (buf), &app->input,
709 						BAR_RL_DEFAULT) > 0) {
710 					reqData.newUsername = strdup (buf);
711 					modified = true;
712 				}
713 				break;
714 			}
715 
716 			case 1: {
717 				/* password */
718 				char buf[80];
719 				BarUiMsg (&app->settings, MSG_QUESTION, "New password: ");
720 				if (BarReadlineStr (buf, sizeof (buf), &app->input,
721 						BAR_RL_NOECHO) > 0) {
722 					reqData.newPassword = strdup (buf);
723 					modified = true;
724 				}
725 				/* write missing newline */
726 				puts ("");
727 				break;
728 			}
729 
730 			case 2: {
731 				/* explicit content filter */
732 				BarUiMsg (&app->settings, MSG_QUESTION,
733 						"Enable explicit content filter? [yn] ");
734 				reqData.explicitContentFilter =
735 						BarReadlineYesNo (settings.explicitContentFilter,
736 						&app->input) ? PIANO_TRUE : PIANO_FALSE;
737 				modified = true;
738 				break;
739 			}
740 
741 			default:
742 				/* continue */
743 				break;
744 		}
745 	}
746 
747 	if (modified) {
748 		reqData.currentUsername = app->settings.username;
749 		reqData.currentPassword = app->settings.password;
750 		BarUiMsg (&app->settings, MSG_INFO, "Changing settings... ");
751 		BarUiActDefaultPianoCall (PIANO_REQUEST_CHANGE_SETTINGS, &reqData);
752 		BarUiActDefaultEventcmd ("settingschange");
753 		/* we want to be able to change settings after a username/password
754 		 * change, so update our internal structs too. the user will have to
755 		 * update his config file by himself though */
756 		if (reqData.newUsername != NULL) {
757 			free (app->settings.username);
758 			app->settings.username = reqData.newUsername;
759 		}
760 		if (reqData.newPassword != NULL) {
761 			free (app->settings.password);
762 			app->settings.password = reqData.newPassword;
763 		}
764 	}
765 }
766 
767 /*	manage station (remove seeds or feedback)
768  */
BarUiActCallback(BarUiActManageStation)769 BarUiActCallback(BarUiActManageStation) {
770 	PianoReturn_t pRet;
771 	CURLcode wRet;
772 	PianoRequestDataGetStationInfo_t reqData;
773 	char selectBuf[2], allowedActions[6], *allowedPos = allowedActions;
774 	char question[128];
775 
776 	memset (&reqData, 0, sizeof (reqData));
777 	reqData.station = selStation;
778 
779 	BarUiMsg (&app->settings, MSG_INFO, "Fetching station info... ");
780 	const bool bret = BarUiActDefaultPianoCall (PIANO_REQUEST_GET_STATION_INFO,
781 			&reqData);
782 	BarUiActDefaultEventcmd ("stationfetchinfo");
783 	if (!bret) {
784 		return;
785 	}
786 
787 	/* enable submenus depending on data availability */
788 	strcpy (question, "Delete ");
789 	if (reqData.info.artistSeeds != NULL) {
790 		strcat (question, "[a]rtist");
791 		*allowedPos++ = 'a';
792 	}
793 	if (reqData.info.songSeeds != NULL) {
794 		if (allowedPos != allowedActions) {
795 			strcat (question, "/");
796 		}
797 		strcat (question, "[s]ong");
798 		*allowedPos++ = 's';
799 	}
800 	if (reqData.info.stationSeeds != NULL) {
801 		if (allowedPos != allowedActions) {
802 			strcat (question, "/");
803 		}
804 		strcat (question, "s[t]ation");
805 		*allowedPos++ = 't';
806 	}
807 	if (allowedPos != allowedActions) {
808 		strcat (question, " seeds");
809 	}
810 	if (reqData.info.feedback != NULL) {
811 		if (allowedPos != allowedActions) {
812 			strcat (question, " or ");
813 		}
814 		strcat (question, "[f]eedback");
815 		*allowedPos++ = 'f';
816 	}
817 	/* station mode is always available */
818 	if (allowedPos != allowedActions) {
819 		strcat (question, "? ");
820 	}
821 	strcat (question, "Manage [m]ode? ");
822 	*allowedPos++ = 'm';
823 
824 	*allowedPos = '\0';
825 
826 	assert (strlen (question) < sizeof (question) / sizeof (*question));
827 
828 	BarUiMsg (&app->settings, MSG_QUESTION, "%s", question);
829 	if (BarReadline (selectBuf, sizeof (selectBuf), allowedActions, &app->input,
830 					BAR_RL_FULLRETURN, -1)) {
831 		if (selectBuf[0] == 'a') {
832 			PianoArtist_t *artist = BarUiSelectArtist (app,
833 					reqData.info.artistSeeds);
834 			if (artist != NULL) {
835 				PianoRequestDataDeleteSeed_t subReqData;
836 
837 				memset (&subReqData, 0, sizeof (subReqData));
838 				subReqData.artist = artist;
839 
840 				BarUiMsg (&app->settings, MSG_INFO, "Deleting artist seed... ");
841 				BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_SEED, &subReqData);
842 				BarUiActDefaultEventcmd ("stationdeleteartistseed");
843 			}
844 		} else if (selectBuf[0] == 's') {
845 			PianoSong_t *song = BarUiSelectSong (app,
846 					reqData.info.songSeeds, &app->input);
847 			if (song != NULL) {
848 				PianoRequestDataDeleteSeed_t subReqData;
849 
850 				memset (&subReqData, 0, sizeof (subReqData));
851 				subReqData.song = song;
852 
853 				BarUiMsg (&app->settings, MSG_INFO, "Deleting song seed... ");
854 				BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_SEED, &subReqData);
855 				BarUiActDefaultEventcmd ("stationdeletesongseed");
856 			}
857 		} else if (selectBuf[0] == 't') {
858 			PianoStation_t *station = BarUiSelectStation (app,
859 					reqData.info.stationSeeds, "Delete seed station: ", NULL,
860 					false);
861 			if (station != NULL) {
862 				PianoRequestDataDeleteSeed_t subReqData;
863 
864 				memset (&subReqData, 0, sizeof (subReqData));
865 				subReqData.station = station;
866 
867 				BarUiMsg (&app->settings, MSG_INFO, "Deleting station seed... ");
868 				BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_SEED, &subReqData);
869 				BarUiActDefaultEventcmd ("stationdeletestationseed");
870 			}
871 		} else if (selectBuf[0] == 'f') {
872 			PianoSong_t *song = BarUiSelectSong (app,
873 					reqData.info.feedback, &app->input);
874 			if (song != NULL) {
875 				BarUiMsg (&app->settings, MSG_INFO, "Deleting feedback... ");
876 				BarUiActDefaultPianoCall (PIANO_REQUEST_DELETE_FEEDBACK, song);
877 				BarUiActDefaultEventcmd ("stationdeletefeedback");
878 			}
879 		} else if (selectBuf[0] == 'm') {
880 			PianoRequestDataGetStationModes_t subReqData =
881 					{ .station = selStation };
882 			BarUiMsg (&app->settings, MSG_INFO, "Fetching modes... ");
883 			BarUiActDefaultPianoCall (PIANO_REQUEST_GET_STATION_MODES,
884 					&subReqData);
885 			BarUiActDefaultEventcmd ("stationgetmodes");
886 
887 			const PianoStationMode_t *curMode = subReqData.retModes;
888 			unsigned int i = 0;
889 			PianoListForeachP (curMode) {
890 				BarUiMsg (&app->settings, MSG_LIST, "%2i) %s: %s%s\n", i,
891 						curMode->name, curMode->description,
892 						curMode->active ? " (active)" : "");
893 				i++;
894 			}
895 
896 			BarUiMsg (&app->settings, MSG_QUESTION, "Pick a new mode: ");
897 			int selected;
898 			while (true) {
899 				if (BarReadlineInt (&selected, &app->input) == 0) {
900 					break;
901 				}
902 
903 				const PianoStationMode_t * const selMode =
904 						PianoListGetP (subReqData.retModes, selected);
905 				if (selMode != NULL) {
906 					PianoRequestDataSetStationMode_t subReqDataSet =
907 							{.station = selStation, .id = selected};
908 					BarUiMsg (&app->settings, MSG_INFO,
909 							"Selecting mode \"%s\"... ", selMode->name);
910 					if (BarUiActDefaultPianoCall (
911 							PIANO_REQUEST_SET_STATION_MODE, &subReqDataSet)) {
912 						drainPlaylist (app);
913 					}
914 					BarUiActDefaultEventcmd ("stationsetmode");
915 					break;
916 				}
917 			}
918 
919 			PianoDestroyStationMode (subReqData.retModes);
920 		}
921 	}
922 
923 	PianoDestroyStationInfo (&reqData.info);
924 }
925