mirror of
https://github.com/SuperBFG7/ympd
synced 2025-10-29 21:03:00 +00:00
better json generator, various fixups
This commit is contained in:
58
src/json_encode.c
Normal file
58
src/json_encode.c
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
// Copyright (c) 2013 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// This library is dual-licensed: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation. For the terms of this
|
||||
// license, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// You are free to use this library under the terms of the GNU General
|
||||
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU General Public License for more details.
|
||||
//
|
||||
// Alternatively, you can license this library under a commercial
|
||||
// license, as set out in <http://cesanta.com/products.html>.
|
||||
|
||||
// json encoder 'frozen' from https://github.com/cesanta/frozen
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "json_encode.h"
|
||||
|
||||
int json_emit_int(char *buf, int buf_len, long int value) {
|
||||
return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%ld", value);
|
||||
}
|
||||
|
||||
int json_emit_double(char *buf, int buf_len, double value) {
|
||||
return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%g", value);
|
||||
}
|
||||
|
||||
int json_emit_quoted_str(char *buf, int buf_len, const char *str) {
|
||||
int i = 0, j = 0, ch;
|
||||
|
||||
#define EMIT(x) do { if (j < buf_len) buf[j++] = x; } while (0)
|
||||
|
||||
EMIT('"');
|
||||
while ((ch = str[i++]) != '\0' && j < buf_len) {
|
||||
switch (ch) {
|
||||
case '"': EMIT('\\'); EMIT('"'); break;
|
||||
case '\\': EMIT('\\'); EMIT('\\'); break;
|
||||
case '\b': EMIT('\\'); EMIT('b'); break;
|
||||
case '\f': EMIT('\\'); EMIT('f'); break;
|
||||
case '\n': EMIT('\\'); EMIT('n'); break;
|
||||
case '\r': EMIT('\\'); EMIT('r'); break;
|
||||
case '\t': EMIT('\\'); EMIT('t'); break;
|
||||
default: EMIT(ch);
|
||||
}
|
||||
}
|
||||
EMIT('"');
|
||||
EMIT(0);
|
||||
|
||||
return j == 0 ? 0 : j - 1;
|
||||
}
|
||||
|
||||
int json_emit_raw_str(char *buf, int buf_len, const char *str) {
|
||||
return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%s", str);
|
||||
}
|
||||
27
src/json_encode.h
Normal file
27
src/json_encode.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/* ympd
|
||||
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||
This project's homepage is: http://www.ympd.org
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef __JSON_ENCODE_H__
|
||||
#define __JSON_ENCODE_H__
|
||||
|
||||
int json_emit_int(char *buf, int buf_len, long int value);
|
||||
int json_emit_double(char *buf, int buf_len, double value);
|
||||
int json_emit_quoted_str(char *buf, int buf_len, const char *str);
|
||||
int json_emit_raw_str(char *buf, int buf_len, const char *str);
|
||||
|
||||
#endif
|
||||
163
src/mpd_client.c
163
src/mpd_client.c
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "mpd_client.h"
|
||||
#include "config.h"
|
||||
#include "json_encode.h"
|
||||
|
||||
const char * mpd_cmd_strs[] = {
|
||||
MPD_CMDS(GEN_STR)
|
||||
@@ -77,9 +78,6 @@ int callback_mpd(struct mg_connection *c)
|
||||
case MPD_API_RM_ALL:
|
||||
mpd_run_clear(mpd.conn);
|
||||
break;
|
||||
case MPD_API_GET_QUEUE:
|
||||
n = mpd_put_queue(mpd.buf);
|
||||
break;
|
||||
case MPD_API_RM_TRACK:
|
||||
if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
|
||||
mpd_run_delete_id(mpd.conn, uint_buf);
|
||||
@@ -112,12 +110,16 @@ int callback_mpd(struct mg_connection *c)
|
||||
if(sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2))
|
||||
mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2);
|
||||
break;
|
||||
case MPD_API_GET_QUEUE:
|
||||
if(sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
|
||||
n = mpd_put_queue(mpd.buf, uint_buf);
|
||||
break;
|
||||
case MPD_API_GET_BROWSE:
|
||||
if(sscanf(c->content, "MPD_API_GET_BROWSE,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||
n = mpd_put_browse(mpd.buf, p_charbuf);
|
||||
else
|
||||
n = mpd_put_browse(mpd.buf, "/");
|
||||
free(p_charbuf);
|
||||
if(sscanf(c->content, "MPD_API_GET_BROWSE,%u,%m[^\t\n]", &uint_buf, &p_charbuf) && p_charbuf != NULL)
|
||||
{
|
||||
n = mpd_put_browse(mpd.buf, p_charbuf, uint_buf);
|
||||
free(p_charbuf);
|
||||
}
|
||||
break;
|
||||
case MPD_API_ADD_TRACK:
|
||||
if(sscanf(c->content, "MPD_API_ADD_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||
@@ -177,7 +179,6 @@ int callback_mpd(struct mg_connection *c)
|
||||
|
||||
mpd.password = p_charbuf;
|
||||
mpd.conn_state = MPD_RECONNECT;
|
||||
printf("Got mpd pw %s\n", mpd.password);
|
||||
return MG_CLIENT_CONTINUE;
|
||||
}
|
||||
break;
|
||||
@@ -309,21 +310,13 @@ void mpd_poll(struct mg_server *s)
|
||||
|
||||
char* mpd_get_title(struct mpd_song const *song)
|
||||
{
|
||||
char *str, *ptr;
|
||||
char *str;
|
||||
|
||||
str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
|
||||
if(str == NULL){
|
||||
str = basename((char *)mpd_song_get_uri(song));
|
||||
}
|
||||
|
||||
if(str == NULL)
|
||||
return NULL;
|
||||
|
||||
ptr = str;
|
||||
while(*ptr++ != '\0')
|
||||
if(*ptr=='"')
|
||||
*ptr='\'';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -373,106 +366,131 @@ int mpd_put_current_song(char *buffer)
|
||||
if(song == NULL)
|
||||
return 0;
|
||||
|
||||
cur += snprintf(cur, end - cur, "{\"type\": \"song_change\", \"data\":"
|
||||
"{\"pos\":%d, \"title\":\"%s\"",
|
||||
mpd_song_get_pos(song),
|
||||
mpd_get_title(song));
|
||||
if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
|
||||
cur += snprintf(cur, end - cur, ", \"artist\":\"%s\"",
|
||||
mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
|
||||
if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
|
||||
cur += snprintf(cur, end - cur, ", \"album\":\"%s\"",
|
||||
mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||
|
||||
cur += snprintf(cur, end - cur, "}}");
|
||||
if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
|
||||
{
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
|
||||
}
|
||||
|
||||
if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
|
||||
{
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
|
||||
}
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "}}");
|
||||
mpd_song_free(song);
|
||||
mpd_response_finish(mpd.conn);
|
||||
|
||||
return cur - buffer;
|
||||
}
|
||||
|
||||
int mpd_put_queue(char *buffer)
|
||||
int mpd_put_queue(char *buffer, unsigned int offset)
|
||||
{
|
||||
char *cur = buffer;
|
||||
const char *end = buffer + MAX_SIZE;
|
||||
struct mpd_entity *entity;
|
||||
|
||||
if (!mpd_send_list_queue_meta(mpd.conn))
|
||||
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE))
|
||||
RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
|
||||
|
||||
cur += snprintf(cur, end - cur, "{\"type\": \"queue\", \"data\": [ ");
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
|
||||
|
||||
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||
const struct mpd_song *song;
|
||||
|
||||
if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
|
||||
song = mpd_entity_get_song(entity);
|
||||
cur += snprintf(cur, end - cur,
|
||||
"{\"id\":%d, \"pos\":%d, \"duration\":%d, \"title\":\"%s\"},",
|
||||
mpd_song_get_id(song),
|
||||
mpd_song_get_pos(song),
|
||||
mpd_song_get_duration(song),
|
||||
mpd_get_title(song)
|
||||
);
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
}
|
||||
mpd_entity_free(entity);
|
||||
}
|
||||
|
||||
/* remove last ',' */
|
||||
cur--;
|
||||
cur += snprintf(cur, end - cur, "] }");
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||
return cur - buffer;
|
||||
}
|
||||
|
||||
int mpd_put_browse(char *buffer, char *path)
|
||||
int mpd_put_browse(char *buffer, char *path, unsigned int offset)
|
||||
{
|
||||
char *cur = buffer;
|
||||
const char *end = buffer + MAX_SIZE;
|
||||
struct mpd_entity *entity;
|
||||
|
||||
unsigned int entity_count = 0;
|
||||
|
||||
if (!mpd_send_list_meta(mpd.conn, path))
|
||||
RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
|
||||
|
||||
cur += snprintf(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
|
||||
|
||||
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||
const struct mpd_song *song;
|
||||
const struct mpd_directory *dir;
|
||||
const struct mpd_playlist *pl;
|
||||
|
||||
if(offset > entity_count)
|
||||
{
|
||||
mpd_entity_free(entity);
|
||||
entity_count++;
|
||||
continue;
|
||||
}
|
||||
else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
|
||||
{
|
||||
mpd_entity_free(entity);
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":");
|
||||
cur += json_emit_int(cur, end - cur, entity_count);
|
||||
cur += json_emit_raw_str(cur, end - cur, "} ");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mpd_entity_get_type(entity)) {
|
||||
case MPD_ENTITY_TYPE_UNKNOWN:
|
||||
break;
|
||||
|
||||
case MPD_ENTITY_TYPE_SONG:
|
||||
song = mpd_entity_get_song(entity);
|
||||
cur += snprintf(cur, end - cur,
|
||||
"{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
|
||||
mpd_song_get_uri(song),
|
||||
mpd_song_get_duration(song),
|
||||
mpd_get_title(song)
|
||||
);
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
break;
|
||||
|
||||
case MPD_ENTITY_TYPE_DIRECTORY:
|
||||
dir = mpd_entity_get_directory(entity);
|
||||
cur += snprintf(cur, end - cur,
|
||||
"{\"type\":\"directory\",\"dir\":\"%s\", \"basename\":\"%s\"},",
|
||||
mpd_directory_get_path(dir),
|
||||
basename((char *)mpd_directory_get_path(dir))
|
||||
);
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"directory\",\"dir\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_directory_get_path(dir));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
break;
|
||||
|
||||
case MPD_ENTITY_TYPE_PLAYLIST:
|
||||
pl = mpd_entity_get_playlist(entity);
|
||||
cur += snprintf(cur, end - cur,
|
||||
"{\"type\":\"playlist\",\"plist\":\"%s\"},",
|
||||
mpd_playlist_get_path(pl)
|
||||
);
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"playlist\",\"plist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_playlist_get_path(pl));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
break;
|
||||
}
|
||||
mpd_entity_free(entity);
|
||||
entity_count++;
|
||||
}
|
||||
|
||||
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
|
||||
@@ -483,12 +501,14 @@ int mpd_put_browse(char *buffer, char *path)
|
||||
|
||||
/* remove last ',' */
|
||||
cur--;
|
||||
cur += snprintf(cur, end - cur, "] }");
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||
return cur - buffer;
|
||||
}
|
||||
|
||||
int mpd_search(char *buffer, char *searchstr)
|
||||
{
|
||||
int i = 0;
|
||||
char *cur = buffer;
|
||||
const char *end = buffer + MAX_SIZE;
|
||||
struct mpd_song *song;
|
||||
@@ -500,21 +520,30 @@ int mpd_search(char *buffer, char *searchstr)
|
||||
else if(mpd_search_commit(mpd.conn) == false)
|
||||
RETURN_ERROR_AND_RECOVER("mpd_search_commit");
|
||||
else {
|
||||
cur += snprintf(cur, end - cur, "{\"type\": \"search\", \"data\": [ ");
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ ");
|
||||
|
||||
while((song = mpd_recv_song(mpd.conn)) != NULL) {
|
||||
cur += snprintf(cur, end - cur,
|
||||
"{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
|
||||
mpd_song_get_uri(song),
|
||||
mpd_song_get_duration(song),
|
||||
mpd_get_title(song)
|
||||
);
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
mpd_song_free(song);
|
||||
|
||||
/* Maximum results */
|
||||
if(i++ >= 300)
|
||||
{
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* remove last ',' */
|
||||
cur--;
|
||||
cur += snprintf(cur, end - cur, "] }");
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||
}
|
||||
return cur - buffer;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
|
||||
|
||||
#define MAX_SIZE 1024 * 100
|
||||
#define MAX_ELEMENTS_PER_PAGE 512
|
||||
|
||||
#define GEN_ENUM(X) X,
|
||||
#define GEN_STR(X) #X,
|
||||
#define MPD_CMDS(X) \
|
||||
@@ -98,8 +100,8 @@ int callback_mpd(struct mg_connection *c);
|
||||
int mpd_close_handler(struct mg_connection *c);
|
||||
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version);
|
||||
int mpd_put_current_song(char *buffer);
|
||||
int mpd_put_queue(char *buffer);
|
||||
int mpd_put_browse(char *buffer, char *path);
|
||||
int mpd_put_queue(char *buffer, unsigned int offset);
|
||||
int mpd_put_browse(char *buffer, char *path, unsigned int offset);
|
||||
int mpd_search(char *buffer, char *searchstr);
|
||||
void mpd_disconnect();
|
||||
#endif
|
||||
|
||||
19
src/ympd.c
19
src/ympd.c
@@ -67,12 +67,12 @@ int main(int argc, char **argv)
|
||||
{"port", required_argument, 0, 'p'},
|
||||
{"webport", required_argument, 0, 'w'},
|
||||
{"user", required_argument, 0, 'u'},
|
||||
{"version", no_argument, 0, 'V'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 0 },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
while((n = getopt_long(argc, argv, "h:p:w:u::V",
|
||||
while((n = getopt_long(argc, argv, "h:p:w:u:v",
|
||||
long_options, &option_index)) != -1) {
|
||||
switch (n) {
|
||||
case 'h':
|
||||
@@ -84,9 +84,10 @@ int main(int argc, char **argv)
|
||||
mg_set_option(server, "listening_port", optarg);
|
||||
break;
|
||||
case 'u':
|
||||
printf("Strarg is %s\n", optarg);
|
||||
mg_set_option(server, "run_as_user", optarg);
|
||||
break;
|
||||
case 'V':
|
||||
case 'v':
|
||||
fprintf(stdout, "ympd %d.%d.%d\n"
|
||||
"Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
|
||||
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
|
||||
@@ -95,12 +96,12 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Usage: %s [OPTION]...\n\n"
|
||||
"\t-h, --host <host>\t\tconnect to mpd at host [localhost]\n"
|
||||
"\t-p, --port <port>\t\tconnect to mpd at port [6600]\n"
|
||||
"\t-w, --webport [ip:]<port>\t\tlisten interface/port for webserver [8080]\n"
|
||||
"\t-u, --user <username>\t\t\tdrop priviliges to user after socket bind\n"
|
||||
"\t-V, --version\t\t\tget version\n"
|
||||
"\t--help\t\t\t\tthis help\n"
|
||||
" -h, --host <host>\t\tconnect to mpd at host [localhost]\n"
|
||||
" -p, --port <port>\t\tconnect to mpd at port [6600]\n"
|
||||
" -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n"
|
||||
" -u, --user <username>\t\tdrop priviliges to user after socket bind\n"
|
||||
" -V, --version\t\t\tget version\n"
|
||||
" --help\t\t\t\tthis help\n"
|
||||
, argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user