mpd_client.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /* ympd
  2. (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
  3. This project's homepage is: http://www.ympd.org
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; version 2 of the License.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License along
  12. with this program; if not, write to the Free Software Foundation, Inc.,
  13. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  14. */
  15. #include <stdio.h>
  16. #include <string.h>
  17. #include <unistd.h>
  18. #include <stdlib.h>
  19. #include <libgen.h>
  20. #include <mpd/client.h>
  21. #include "mpd_client.h"
  22. #include "config.h"
  23. const char * mpd_cmd_strs[] = {
  24. MPD_CMDS(GEN_STR)
  25. };
  26. static inline enum mpd_cmd_ids get_cmd_id(char *cmd)
  27. {
  28. for(int i = 0; i < sizeof(mpd_cmd_strs)/sizeof(mpd_cmd_strs[0]); i++)
  29. if(!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
  30. return i;
  31. return -1;
  32. }
  33. int callback_mpd(struct mg_connection *c)
  34. {
  35. enum mpd_cmd_ids cmd_id = get_cmd_id(c->content);
  36. size_t n = 0;
  37. unsigned int uint_buf, uint_buf_2;
  38. int int_buf;
  39. char *p_charbuf;
  40. if(cmd_id == -1)
  41. return MG_CLIENT_CONTINUE;
  42. if(mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST &&
  43. cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS)
  44. return MG_CLIENT_CONTINUE;
  45. switch(cmd_id)
  46. {
  47. case MPD_API_UPDATE_DB:
  48. mpd_run_update(mpd.conn, NULL);
  49. break;
  50. case MPD_API_SET_PAUSE:
  51. mpd_run_toggle_pause(mpd.conn);
  52. break;
  53. case MPD_API_SET_PREV:
  54. mpd_run_previous(mpd.conn);
  55. break;
  56. case MPD_API_SET_NEXT:
  57. mpd_run_next(mpd.conn);
  58. break;
  59. case MPD_API_SET_PLAY:
  60. mpd_run_play(mpd.conn);
  61. break;
  62. case MPD_API_SET_STOP:
  63. mpd_run_stop(mpd.conn);
  64. break;
  65. case MPD_API_RM_ALL:
  66. mpd_run_clear(mpd.conn);
  67. break;
  68. case MPD_API_GET_QUEUE:
  69. n = mpd_put_queue(mpd.buf);
  70. break;
  71. case MPD_API_RM_TRACK:
  72. if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
  73. mpd_run_delete_id(mpd.conn, uint_buf);
  74. break;
  75. case MPD_API_PLAY_TRACK:
  76. if(sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
  77. mpd_run_play_id(mpd.conn, uint_buf);
  78. break;
  79. case MPD_API_TOGGLE_RANDOM:
  80. if(sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf))
  81. mpd_run_random(mpd.conn, uint_buf);
  82. break;
  83. case MPD_API_TOGGLE_REPEAT:
  84. if(sscanf(c->content, "MPD_API_TOGGLE_REPEAT,%u", &uint_buf))
  85. mpd_run_repeat(mpd.conn, uint_buf);
  86. break;
  87. case MPD_API_TOGGLE_CONSUME:
  88. if(sscanf(c->content, "MPD_API_TOGGLE_CONSUME,%u", &uint_buf))
  89. mpd_run_consume(mpd.conn, uint_buf);
  90. break;
  91. case MPD_API_TOGGLE_SINGLE:
  92. if(sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf))
  93. mpd_run_single(mpd.conn, uint_buf);
  94. break;
  95. case MPD_API_SET_VOLUME:
  96. if(sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100)
  97. mpd_run_set_volume(mpd.conn, uint_buf);
  98. break;
  99. case MPD_API_SET_SEEK:
  100. if(sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2))
  101. mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2);
  102. break;
  103. case MPD_API_GET_BROWSE:
  104. if(sscanf(c->content, "MPD_API_GET_BROWSE,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
  105. n = mpd_put_browse(mpd.buf, p_charbuf);
  106. else
  107. n = mpd_put_browse(mpd.buf, "/");
  108. free(p_charbuf);
  109. break;
  110. case MPD_API_ADD_TRACK:
  111. if(sscanf(c->content, "MPD_API_ADD_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
  112. {
  113. mpd_run_add(mpd.conn, p_charbuf);
  114. free(p_charbuf);
  115. }
  116. break;
  117. case MPD_API_ADD_PLAY_TRACK:
  118. if(sscanf(c->content, "MPD_API_ADD_PLAY_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
  119. {
  120. int_buf = mpd_run_add_id(mpd.conn, p_charbuf);
  121. if(int_buf != -1)
  122. mpd_run_play_id(mpd.conn, int_buf);
  123. free(p_charbuf);
  124. }
  125. break;
  126. case MPD_API_ADD_PLAYLIST:
  127. if(sscanf(c->content, "MPD_API_ADD_PLAYLIST,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
  128. {
  129. mpd_run_load(mpd.conn, p_charbuf);
  130. free(p_charbuf);
  131. }
  132. break;
  133. case MPD_API_SEARCH:
  134. if(sscanf(c->content, "MPD_API_SEARCH,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
  135. {
  136. n = mpd_search(mpd.buf, p_charbuf);
  137. free(p_charbuf);
  138. }
  139. break;
  140. #ifdef WITH_MPD_HOST_CHANGE
  141. /* Commands allowed when disconnected from MPD server */
  142. case MPD_API_SET_MPDHOST:
  143. int_buf = 0;
  144. if(sscanf(c->content, "MPD_API_SET_MPDHOST,%d,%m[^\t\n ]", &int_buf, &p_charbuf) &&
  145. p_charbuf != NULL && int_buf > 0)
  146. {
  147. strncpy(mpd.host, p_charbuf, sizeof(mpd.host));
  148. free(p_charbuf);
  149. mpd.port = int_buf;
  150. mpd.conn_state = MPD_RECONNECT;
  151. return MG_CLIENT_CONTINUE;
  152. }
  153. break;
  154. case MPD_API_GET_MPDHOST:
  155. n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": "
  156. "{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}"
  157. "}", mpd.host, mpd.port, mpd.password ? "true" : "false");
  158. break;
  159. case MPD_API_SET_MPDPASS:
  160. if(sscanf(c->content, "MPD_API_SET_MPDPASS,%m[^\t\n ]", &p_charbuf) &&
  161. p_charbuf != NULL)
  162. {
  163. if(mpd.password)
  164. free(mpd.password);
  165. mpd.password = p_charbuf;
  166. mpd.conn_state = MPD_RECONNECT;
  167. printf("Got mpd pw %s\n", mpd.password);
  168. return MG_CLIENT_CONTINUE;
  169. }
  170. break;
  171. #endif
  172. }
  173. if(mpd.conn_state == MPD_CONNECTED && mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS)
  174. {
  175. n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}",
  176. mpd_connection_get_error_message(mpd.conn));
  177. /* Try to recover error */
  178. if (!mpd_connection_clear_error(mpd.conn))
  179. mpd.conn_state = MPD_FAILURE;
  180. }
  181. if(n > 0)
  182. mg_websocket_write(c, 1, mpd.buf, n);
  183. return MG_CLIENT_CONTINUE;
  184. }
  185. int mpd_close_handler(struct mg_connection *c)
  186. {
  187. /* Cleanup session data */
  188. if(c->connection_param)
  189. free(c->connection_param);
  190. return 0;
  191. }
  192. static int mpd_notify_callback(struct mg_connection *c) {
  193. size_t n;
  194. if(!c->is_websocket)
  195. return MG_REQUEST_PROCESSED;
  196. if(c->callback_param)
  197. {
  198. /* error message? */
  199. n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",
  200. (const char *)c->callback_param);
  201. mg_websocket_write(c, 1, mpd.buf, n);
  202. return MG_REQUEST_PROCESSED;
  203. }
  204. if(!c->connection_param)
  205. c->connection_param = calloc(1, sizeof(struct t_mpd_client_session));
  206. struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
  207. if(mpd.conn_state != MPD_CONNECTED) {
  208. n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}");
  209. mg_websocket_write(c, 1, mpd.buf, n);
  210. }
  211. else
  212. {
  213. mg_websocket_write(c, 1, mpd.buf, mpd.buf_size);
  214. if(s->song_id != mpd.song_id)
  215. {
  216. n = mpd_put_current_song(mpd.buf);
  217. mg_websocket_write(c, 1, mpd.buf, n);
  218. s->song_id = mpd.song_id;
  219. }
  220. if(s->queue_version != mpd.queue_version)
  221. {
  222. n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_queue\"}");
  223. mg_websocket_write(c, 1, mpd.buf, n);
  224. s->queue_version = mpd.queue_version;
  225. }
  226. }
  227. return MG_REQUEST_PROCESSED;
  228. }
  229. void mpd_poll(struct mg_server *s)
  230. {
  231. switch (mpd.conn_state) {
  232. case MPD_DISCONNECTED:
  233. /* Try to connect */
  234. fprintf(stdout, "MPD Connecting to %s:%d\n", mpd.host, mpd.port);
  235. mpd.conn = mpd_connection_new(mpd.host, mpd.port, 3000);
  236. if (mpd.conn == NULL) {
  237. fprintf(stderr, "Out of memory.");
  238. mpd.conn_state = MPD_FAILURE;
  239. return;
  240. }
  241. if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
  242. fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
  243. mg_iterate_over_connections(s, mpd_notify_callback,
  244. (void *)mpd_connection_get_error_message(mpd.conn));
  245. mpd.conn_state = MPD_FAILURE;
  246. return;
  247. }
  248. if(mpd.password && !mpd_run_password(mpd.conn, mpd.password))
  249. {
  250. fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
  251. mg_iterate_over_connections(s, mpd_notify_callback,
  252. (void *)mpd_connection_get_error_message(mpd.conn));
  253. mpd.conn_state = MPD_FAILURE;
  254. return;
  255. }
  256. fprintf(stderr, "MPD connected.\n");
  257. mpd.conn_state = MPD_CONNECTED;
  258. break;
  259. case MPD_FAILURE:
  260. fprintf(stderr, "MPD connection failed.\n");
  261. case MPD_DISCONNECT:
  262. case MPD_RECONNECT:
  263. if(mpd.conn != NULL)
  264. mpd_connection_free(mpd.conn);
  265. mpd.conn = NULL;
  266. mpd.conn_state = MPD_DISCONNECTED;
  267. break;
  268. case MPD_CONNECTED:
  269. mpd.buf_size = mpd_put_state(mpd.buf, &mpd.song_id, &mpd.queue_version);
  270. mg_iterate_over_connections(s, mpd_notify_callback, NULL);
  271. break;
  272. }
  273. }
  274. char* mpd_get_title(struct mpd_song const *song)
  275. {
  276. char *str, *ptr;
  277. str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
  278. if(str == NULL){
  279. str = basename((char *)mpd_song_get_uri(song));
  280. }
  281. if(str == NULL)
  282. return NULL;
  283. ptr = str;
  284. while(*ptr++ != '\0')
  285. if(*ptr=='"')
  286. *ptr='\'';
  287. return str;
  288. }
  289. int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
  290. {
  291. struct mpd_status *status;
  292. int len;
  293. status = mpd_run_status(mpd.conn);
  294. if (!status) {
  295. fprintf(stderr, "MPD mpd_run_status: %s\n", mpd_connection_get_error_message(mpd.conn));
  296. mpd.conn_state = MPD_FAILURE;
  297. return 0;
  298. }
  299. len = snprintf(buffer, MAX_SIZE,
  300. "{\"type\":\"state\", \"data\":{"
  301. " \"state\":%d, \"volume\":%d, \"repeat\":%d,"
  302. " \"single\":%d, \"consume\":%d, \"random\":%d, "
  303. " \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
  304. " \"currentsongid\": %d"
  305. "}}",
  306. mpd_status_get_state(status),
  307. mpd_status_get_volume(status),
  308. mpd_status_get_repeat(status),
  309. mpd_status_get_single(status),
  310. mpd_status_get_consume(status),
  311. mpd_status_get_random(status),
  312. mpd_status_get_song_pos(status),
  313. mpd_status_get_elapsed_time(status),
  314. mpd_status_get_total_time(status),
  315. mpd_status_get_song_id(status));
  316. *current_song_id = mpd_status_get_song_id(status);
  317. *queue_version = mpd_status_get_queue_version(status);
  318. mpd_status_free(status);
  319. return len;
  320. }
  321. int mpd_put_current_song(char *buffer)
  322. {
  323. char *cur = buffer;
  324. const char *end = buffer + MAX_SIZE;
  325. struct mpd_song *song;
  326. song = mpd_run_current_song(mpd.conn);
  327. if(song == NULL)
  328. return 0;
  329. cur += snprintf(cur, end - cur, "{\"type\": \"song_change\", \"data\":"
  330. "{\"pos\":%d, \"title\":\"%s\"",
  331. mpd_song_get_pos(song),
  332. mpd_get_title(song));
  333. if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
  334. cur += snprintf(cur, end - cur, ", \"artist\":\"%s\"",
  335. mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
  336. if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
  337. cur += snprintf(cur, end - cur, ", \"album\":\"%s\"",
  338. mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
  339. cur += snprintf(cur, end - cur, "}}");
  340. mpd_song_free(song);
  341. mpd_response_finish(mpd.conn);
  342. return cur - buffer;
  343. }
  344. int mpd_put_queue(char *buffer)
  345. {
  346. char *cur = buffer;
  347. const char *end = buffer + MAX_SIZE;
  348. struct mpd_entity *entity;
  349. if (!mpd_send_list_queue_meta(mpd.conn))
  350. RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
  351. cur += snprintf(cur, end - cur, "{\"type\": \"queue\", \"data\": [ ");
  352. while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
  353. const struct mpd_song *song;
  354. if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
  355. song = mpd_entity_get_song(entity);
  356. cur += snprintf(cur, end - cur,
  357. "{\"id\":%d, \"pos\":%d, \"duration\":%d, \"title\":\"%s\"},",
  358. mpd_song_get_id(song),
  359. mpd_song_get_pos(song),
  360. mpd_song_get_duration(song),
  361. mpd_get_title(song)
  362. );
  363. }
  364. mpd_entity_free(entity);
  365. }
  366. /* remove last ',' */
  367. cur--;
  368. cur += snprintf(cur, end - cur, "] }");
  369. return cur - buffer;
  370. }
  371. int mpd_put_browse(char *buffer, char *path)
  372. {
  373. char *cur = buffer;
  374. const char *end = buffer + MAX_SIZE;
  375. struct mpd_entity *entity;
  376. if (!mpd_send_list_meta(mpd.conn, path))
  377. RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
  378. cur += snprintf(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
  379. while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
  380. const struct mpd_song *song;
  381. const struct mpd_directory *dir;
  382. const struct mpd_playlist *pl;
  383. switch (mpd_entity_get_type(entity)) {
  384. case MPD_ENTITY_TYPE_UNKNOWN:
  385. break;
  386. case MPD_ENTITY_TYPE_SONG:
  387. song = mpd_entity_get_song(entity);
  388. cur += snprintf(cur, end - cur,
  389. "{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
  390. mpd_song_get_uri(song),
  391. mpd_song_get_duration(song),
  392. mpd_get_title(song)
  393. );
  394. break;
  395. case MPD_ENTITY_TYPE_DIRECTORY:
  396. dir = mpd_entity_get_directory(entity);
  397. cur += snprintf(cur, end - cur,
  398. "{\"type\":\"directory\",\"dir\":\"%s\", \"basename\":\"%s\"},",
  399. mpd_directory_get_path(dir),
  400. basename((char *)mpd_directory_get_path(dir))
  401. );
  402. break;
  403. case MPD_ENTITY_TYPE_PLAYLIST:
  404. pl = mpd_entity_get_playlist(entity);
  405. cur += snprintf(cur, end - cur,
  406. "{\"type\":\"playlist\",\"plist\":\"%s\"},",
  407. mpd_playlist_get_path(pl)
  408. );
  409. break;
  410. }
  411. mpd_entity_free(entity);
  412. }
  413. if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
  414. fprintf(stderr, "MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(mpd.conn));
  415. mpd.conn_state = MPD_FAILURE;
  416. return 0;
  417. }
  418. /* remove last ',' */
  419. cur--;
  420. cur += snprintf(cur, end - cur, "] }");
  421. return cur - buffer;
  422. }
  423. int mpd_search(char *buffer, char *searchstr)
  424. {
  425. char *cur = buffer;
  426. const char *end = buffer + MAX_SIZE;
  427. struct mpd_song *song;
  428. if(mpd_search_db_songs(mpd.conn, false) == false)
  429. RETURN_ERROR_AND_RECOVER("mpd_search_db_songs");
  430. else if(mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false)
  431. RETURN_ERROR_AND_RECOVER("mpd_search_add_any_tag_constraint");
  432. else if(mpd_search_commit(mpd.conn) == false)
  433. RETURN_ERROR_AND_RECOVER("mpd_search_commit");
  434. else {
  435. cur += snprintf(cur, end - cur, "{\"type\": \"search\", \"data\": [ ");
  436. while((song = mpd_recv_song(mpd.conn)) != NULL) {
  437. cur += snprintf(cur, end - cur,
  438. "{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
  439. mpd_song_get_uri(song),
  440. mpd_song_get_duration(song),
  441. mpd_get_title(song)
  442. );
  443. mpd_song_free(song);
  444. }
  445. /* remove last ',' */
  446. cur--;
  447. cur += snprintf(cur, end - cur, "] }");
  448. }
  449. return cur - buffer;
  450. }
  451. void mpd_disconnect()
  452. {
  453. mpd.conn_state = MPD_DISCONNECT;
  454. mpd_poll(NULL);
  455. }