mpd_client.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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 = NULL;
  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. {
  164. if(mpd.password)
  165. free(mpd.password);
  166. mpd.password = p_charbuf;
  167. mpd.conn_state = MPD_RECONNECT;
  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;
  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. return str;
  282. }
  283. int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
  284. {
  285. struct mpd_status *status;
  286. int len;
  287. status = mpd_run_status(mpd.conn);
  288. if (!status) {
  289. fprintf(stderr, "MPD mpd_run_status: %s\n", mpd_connection_get_error_message(mpd.conn));
  290. mpd.conn_state = MPD_FAILURE;
  291. return 0;
  292. }
  293. len = snprintf(buffer, MAX_SIZE,
  294. "{\"type\":\"state\", \"data\":{"
  295. " \"state\":%d, \"volume\":%d, \"repeat\":%d,"
  296. " \"single\":%d, \"consume\":%d, \"random\":%d, "
  297. " \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
  298. " \"currentsongid\": %d"
  299. "}}",
  300. mpd_status_get_state(status),
  301. mpd_status_get_volume(status),
  302. mpd_status_get_repeat(status),
  303. mpd_status_get_single(status),
  304. mpd_status_get_consume(status),
  305. mpd_status_get_random(status),
  306. mpd_status_get_song_pos(status),
  307. mpd_status_get_elapsed_time(status),
  308. mpd_status_get_total_time(status),
  309. mpd_status_get_song_id(status));
  310. *current_song_id = mpd_status_get_song_id(status);
  311. *queue_version = mpd_status_get_queue_version(status);
  312. mpd_status_free(status);
  313. return len;
  314. }
  315. int mpd_put_current_song(char *buffer)
  316. {
  317. char *cur = buffer;
  318. const char *end = buffer + MAX_SIZE;
  319. struct mpd_song *song;
  320. song = mpd_run_current_song(mpd.conn);
  321. if(song == NULL)
  322. return 0;
  323. cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
  324. cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
  325. cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
  326. cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
  327. if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
  328. {
  329. cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
  330. cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
  331. }
  332. if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
  333. {
  334. cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
  335. cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
  336. }
  337. cur += json_emit_raw_str(cur, end - cur, "}}");
  338. mpd_song_free(song);
  339. mpd_response_finish(mpd.conn);
  340. return cur - buffer;
  341. }
  342. int mpd_put_queue(char *buffer, unsigned int offset)
  343. {
  344. char *cur = buffer;
  345. const char *end = buffer + MAX_SIZE;
  346. struct mpd_entity *entity;
  347. if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE))
  348. RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
  349. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
  350. while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
  351. const struct mpd_song *song;
  352. if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
  353. song = mpd_entity_get_song(entity);
  354. cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
  355. cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
  356. cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
  357. cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
  358. cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
  359. cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
  360. cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
  361. cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
  362. cur += json_emit_raw_str(cur, end - cur, "},");
  363. }
  364. mpd_entity_free(entity);
  365. }
  366. /* remove last ',' */
  367. cur--;
  368. cur += json_emit_raw_str(cur, end - cur, "]}");
  369. return cur - buffer;
  370. }
  371. int mpd_put_browse(char *buffer, char *path, unsigned int offset)
  372. {
  373. char *cur = buffer;
  374. const char *end = buffer + MAX_SIZE;
  375. struct mpd_entity *entity;
  376. unsigned int entity_count = 0;
  377. if (!mpd_send_list_meta(mpd.conn, path))
  378. RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
  379. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
  380. while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
  381. const struct mpd_song *song;
  382. const struct mpd_directory *dir;
  383. const struct mpd_playlist *pl;
  384. if(offset > entity_count)
  385. {
  386. mpd_entity_free(entity);
  387. entity_count++;
  388. continue;
  389. }
  390. else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
  391. {
  392. mpd_entity_free(entity);
  393. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":");
  394. cur += json_emit_int(cur, end - cur, entity_count);
  395. cur += json_emit_raw_str(cur, end - cur, "} ");
  396. break;
  397. }
  398. switch (mpd_entity_get_type(entity)) {
  399. case MPD_ENTITY_TYPE_UNKNOWN:
  400. break;
  401. case MPD_ENTITY_TYPE_SONG:
  402. song = mpd_entity_get_song(entity);
  403. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
  404. cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
  405. cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
  406. cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
  407. cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
  408. cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
  409. cur += json_emit_raw_str(cur, end - cur, "},");
  410. break;
  411. case MPD_ENTITY_TYPE_DIRECTORY:
  412. dir = mpd_entity_get_directory(entity);
  413. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"directory\",\"dir\":");
  414. cur += json_emit_quoted_str(cur, end - cur, mpd_directory_get_path(dir));
  415. cur += json_emit_raw_str(cur, end - cur, "},");
  416. break;
  417. case MPD_ENTITY_TYPE_PLAYLIST:
  418. pl = mpd_entity_get_playlist(entity);
  419. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"playlist\",\"plist\":");
  420. cur += json_emit_quoted_str(cur, end - cur, mpd_playlist_get_path(pl));
  421. cur += json_emit_raw_str(cur, end - cur, "},");
  422. break;
  423. }
  424. mpd_entity_free(entity);
  425. entity_count++;
  426. }
  427. if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
  428. fprintf(stderr, "MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(mpd.conn));
  429. mpd.conn_state = MPD_FAILURE;
  430. return 0;
  431. }
  432. /* remove last ',' */
  433. cur--;
  434. cur += json_emit_raw_str(cur, end - cur, "]}");
  435. return cur - buffer;
  436. }
  437. int mpd_search(char *buffer, char *searchstr)
  438. {
  439. int i = 0;
  440. char *cur = buffer;
  441. const char *end = buffer + MAX_SIZE;
  442. struct mpd_song *song;
  443. if(mpd_search_db_songs(mpd.conn, false) == false)
  444. RETURN_ERROR_AND_RECOVER("mpd_search_db_songs");
  445. else if(mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false)
  446. RETURN_ERROR_AND_RECOVER("mpd_search_add_any_tag_constraint");
  447. else if(mpd_search_commit(mpd.conn) == false)
  448. RETURN_ERROR_AND_RECOVER("mpd_search_commit");
  449. else {
  450. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ ");
  451. while((song = mpd_recv_song(mpd.conn)) != NULL) {
  452. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
  453. cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
  454. cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
  455. cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
  456. cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
  457. cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
  458. cur += json_emit_raw_str(cur, end - cur, "},");
  459. mpd_song_free(song);
  460. /* Maximum results */
  461. if(i++ >= 300)
  462. {
  463. cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
  464. break;
  465. }
  466. }
  467. /* remove last ',' */
  468. cur--;
  469. cur += json_emit_raw_str(cur, end - cur, "]}");
  470. }
  471. return cur - buffer;
  472. }
  473. void mpd_disconnect()
  474. {
  475. mpd.conn_state = MPD_DISCONNECT;
  476. mpd_poll(NULL);
  477. }