mpd_client.c 19 KB

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