1
0
mirror of https://github.com/SuperBFG7/ympd synced 2025-07-05 19:32:52 +00:00

Feat: initial working code

This commit is contained in:
jcorporation 2019-01-05 00:01:34 +00:00
parent c7f5357f93
commit 97e4fe2e94
15 changed files with 582 additions and 300 deletions

View File

@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 2.6)
project (mympd C)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
set(CPACK_PACKAGE_VERSION_MAJOR "4")
set(CPACK_PACKAGE_VERSION_MINOR "7")
set(CPACK_PACKAGE_VERSION_PATCH "2")
set(CPACK_PACKAGE_VERSION_MAJOR "5")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
if(CMAKE_BUILD_TYPE MATCHES RELEASE)
set(ASSETS_PATH "/usr/share/${PROJECT_NAME}/htdocs")
@ -21,7 +21,7 @@ include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_I
include(CheckCSourceCompiles)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_ENABLE_SSL -D MG_ENABLE_IPV6 -D MG_DISABLE_MQTT -D MG_DISABLE_MQTT_BROKER -D MG_DISABLE_DNS_SERVER -D MG_DISABLE_COAP -D MG_DISABLE_HTTP_CGI -D MG_DISABLE_HTTP_SSI -D MG_DISABLE_HTTP_WEBDAV")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_ENABLE_SSL -D MG_ENABLE_THREADS -D MG_ENABLE_IPV6 -D MG_DISABLE_MQTT -D MG_DISABLE_MQTT_BROKER -D MG_DISABLE_DNS_SERVER -D MG_DISABLE_COAP -D MG_DISABLE_HTTP_CGI -D MG_DISABLE_HTTP_SSI -D MG_DISABLE_HTTP_WEBDAV")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -D_FORTIFY_SOURCE=2 -fstack-protector -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null -fsanitize=return -fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=bounds-strict -fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=bool -fsanitize=enum -fsanitize=vptr -static-libasan")
find_package(OpenSSL REQUIRED)
@ -31,8 +31,10 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_SSL)
set(SOURCES
src/mympd.c
src/mpd_client.c
src/web_server.c
src/list.c
src/validate.c
src/tiny_queue.c
src/common.c
dist/src/mongoose/mongoose.c
dist/src/frozen/frozen.c
dist/src/inih/ini.c

View File

@ -4,7 +4,7 @@
pkgname=mympd
_pkgname=myMPD
pkgver=4.7.2
pkgver=5.0.0
pkgrel=1
pkgdesc="myMPD is a standalone and mobile friendly web mpdclient."
arch=('x86_64' 'armv7h' 'aarch64')

View File

@ -4,7 +4,7 @@
# (c) 2018 Juergen Mang <mail@jcgames.de>
Name: myMPD
Version: 4.7.2
Version: 5.0.0
Release: 0
License: GPL-2.0
Group: Productivity/Multimedia/Sound/Players

4
debian/changelog vendored
View File

@ -1,5 +1,5 @@
mympd (4.7.2-1) stable; urgency=medium
mympd (5.0.0-1) stable; urgency=medium
* Release from master
-- Juergen Mang <mail@jcgames.de> Fri, 07 Dec 2018 17:12:12 +0000
-- Juergen Mang <mail@jcgames.de> Fri, 04 Jan 2019 09:01:07 +0000

View File

@ -1,4 +1,4 @@
var CACHE = 'myMPD-cache-v4.7.2';
var CACHE = 'myMPD-cache-v5.0.0';
var urlsToCache = [
'/',
'/player.html',

View File

@ -1,5 +1,5 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
@ -26,7 +26,7 @@
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include "validate.h"
#include "common.h"
void sanitize_string(const char *data) {
static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz"
@ -38,18 +38,11 @@ void sanitize_string(const char *data) {
*cp = '_';
}
int validate_path(char *path, const char *basepath) {
char *rpath = NULL;
char *ptr;
ptr = realpath(path, rpath);
if (ptr == NULL)
return 1;
if (strncmp(basepath, ptr, strlen(basepath)) == 0) {
free(rpath);
int copy_string(char * const dest, char const * const src, size_t const dst_len, size_t const src_len) {
if (dst_len == 0 || src_len == 0)
return 0;
}
else {
free(rpath);
return 1;
}
size_t const max = (src_len < dst_len) ? src_len : dst_len -1;
memcpy(dest, src, max);
dest[max] = '\0';
return max;
}

72
src/common.h Normal file
View File

@ -0,0 +1,72 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
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 __COMMON_H__
#define __COMMON_H__
#include <stdbool.h>
#include <stdlib.h>
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
#define LOG_INFO() if (config.loglevel >= 1)
#define LOG_VERBOSE() if (config.loglevel >= 2)
#define LOG_DEBUG() if (config.loglevel == 3)
typedef struct {
long mpdport;
const char *mpdhost;
const char *mpdpass;
const char *webport;
bool ssl;
const char *sslport;
const char *sslcert;
const char *sslkey;
const char *user;
bool coverimage;
const char *coverimagename;
long coverimagesize;
bool stickers;
bool mixramp;
const char *taglist;
const char *searchtaglist;
const char *browsetaglist;
bool smartpls;
const char *varlibdir;
const char *etcdir;
unsigned long max_elements_per_page;
bool syscmds;
bool localplayer;
long streamport;
const char *streamurl;
unsigned long last_played_count;
long loglevel;
} t_config;
t_config config;
void sanitize_string(const char *data);
int copy_string(char * const dest, char const * const src, size_t const dst_len, size_t const src_len);
#endif

View File

@ -18,6 +18,9 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __LIST_H__
#define __LIST_H__
struct node {
char *data;
long value;
@ -43,3 +46,4 @@ int list_shuffle(struct list *l);
int list_sort_by_value(struct list *l, bool order);
int list_swap_item(struct node *n1, struct node *n2);
struct node *list_node_at(const struct list * l, unsigned index);
#endif

View File

@ -32,8 +32,9 @@
#include <poll.h>
#include <mpd/client.h>
#include "common.h"
#include "mpd_client.h"
#include "validate.h"
#include "web_server.h"
#include "config.h"
#include "../dist/src/frozen/frozen.h"
@ -49,9 +50,7 @@ static inline enum mpd_cmd_ids get_cmd_id(const char *cmd) {
return -1;
}
enum mpd_idle idle_bitmask_save = 0;
void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
void mympd_api(struct work_request_t *request) {
size_t n = 0;
char *cmd;
unsigned int uint_buf1, uint_buf2, uint_rc;
@ -61,49 +60,40 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
char *p_charbuf1, *p_charbuf2, *p_charbuf3, *p_charbuf4;
char p_char[4];
enum mpd_cmd_ids cmd_id;
struct pollfd fds[1];
int pollrc;
#ifdef DEBUG
struct timespec start, end;
#endif
LOG_VERBOSE() printf("API request: %s\n", msg.p);
LOG_VERBOSE() printf("API request: %.*s\n", request->length, request->data);
je = json_scanf(msg.p, msg.len, "{cmd: %Q}", &cmd);
if (je == 1)
je = json_scanf(request->data, request->length, "{cmd: %Q}", &cmd);
if (je == 1) {
cmd_id = get_cmd_id(cmd);
else
cmd_id = get_cmd_id("MPD_API_UNKNOWN");
}
else {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Invalid API request.\"}");
printf("Error: Invalid API request.\n");
struct work_result_t *response = (struct work_result_t*)malloc(sizeof(struct work_result_t));
response->conn_id = request->conn_id;
response->length = copy_string(response->data, mpd.buf, MAX_SIZE, n);
tiny_queue_push(web_server_queue, response);
return;
}
if (cmd_id == -1)
cmd_id = get_cmd_id("MPD_API_UNKNOWN");
LOG_DEBUG() fprintf(stderr, "DEBUG: Leaving mpd idle mode\n");
if (mpd_send_noidle(mpd.conn)) {
//save idle events (processing later)
LOG_DEBUG() fprintf(stderr, "DEBUG: Checking for idle events\n");
fds[0].fd = mpd_connection_get_fd(mpd.conn);
fds[0].events = POLLIN;
pollrc = poll(fds, 1, 200);
if (pollrc > 0) {
idle_bitmask_save = mpd_recv_idle(mpd.conn, false);
if (idle_bitmask_save > 0)
LOG_DEBUG() fprintf(stderr, "DEBUG: Idle event before request: %d\n", idle_bitmask_save);
}
}
mpd_response_finish(mpd.conn);
//handle request
#ifdef DEBUG
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
#endif
switch(cmd_id) {
case MPD_API_UNKNOWN:
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown request\"}");
printf("Unknown API request: %s\n", msg.p);
printf("Unknown API request: %.*s\n", request->length, request->data);
break;
case MPD_API_LIKE:
if (config.stickers) {
je = json_scanf(msg.p, msg.len, "{data: {uri: %Q, like: %d}}", &p_charbuf1, &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {uri: %Q, like: %d}}", &p_charbuf1, &uint_buf1);
if (je == 2) {
if (!mympd_like_song_uri(p_charbuf1, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set like.\"}");
@ -118,10 +108,10 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_COLS_SAVE:
je = json_scanf(msg.p, msg.len, "{data: {table: %Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {table: %Q}}", &p_charbuf1);
if (je == 1) {
char column_list[800];
snprintf(column_list, 800, "%.*s", msg.len, msg.p);
snprintf(column_list, 800, "%.*s", request->length, request->data);
char *cols = strchr(column_list, '[');
int len = strlen(cols);
if (len > 1)
@ -169,7 +159,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
break;
case MPD_API_SYSCMD:
if (config.syscmds == true) {
je = json_scanf(msg.p, msg.len, "{data: {cmd: %Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {cmd: %Q}}", &p_charbuf1);
if (je == 1) {
int_buf1 = list_get_value(&syscmds, p_charbuf1);
if (int_buf1 > -1)
@ -188,73 +178,73 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = mympd_put_state(mpd.buf, &mpd.song_id, &mpd.next_song_id, &mpd.last_song_id, &mpd.queue_version, &mpd.queue_length);
break;
case MPD_API_SETTINGS_SET:
je = json_scanf(msg.p, msg.len, "{data: {notificationWeb: %B}}", &mympd_state.notificationWeb);
je = json_scanf(request->data, request->length, "{data: {notificationWeb: %B}}", &mympd_state.notificationWeb);
if (je == 1)
if (!mympd_state_set("notificationWeb", (mympd_state.notificationWeb == true ? "true" : "false")))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationWeb.\"}");
je = json_scanf(msg.p, msg.len, "{data: {notificationPage: %B}}", &mympd_state.notificationPage);
je = json_scanf(request->data, request->length, "{data: {notificationPage: %B}}", &mympd_state.notificationPage);
if (je == 1)
if (!mympd_state_set("notificationPage", (mympd_state.notificationPage == true ? "true" : "false")))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationPage.\"}");
je = json_scanf(msg.p, msg.len, "{data: {jukeboxMode: %d}}", &mympd_state.jukeboxMode);
je = json_scanf(request->data, request->length, "{data: {jukeboxMode: %d}}", &mympd_state.jukeboxMode);
if (je == 1) {
snprintf(p_char, 4, "%d", mympd_state.jukeboxMode);
if (!mympd_state_set("jukeboxMode", p_char))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxMode.\"}");
}
je = json_scanf(msg.p, msg.len, "{data: {jukeboxPlaylist: %Q}}", &mympd_state.jukeboxPlaylist);
je = json_scanf(request->data, request->length, "{data: {jukeboxPlaylist: %Q}}", &mympd_state.jukeboxPlaylist);
if (je == 1)
if (!mympd_state_set("jukeboxPlaylist", mympd_state.jukeboxPlaylist))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxPlaylist.\"}");
je = json_scanf(msg.p, msg.len, "{data: {jukeboxQueueLength: %d}}", &mympd_state.jukeboxQueueLength);
je = json_scanf(request->data, request->length, "{data: {jukeboxQueueLength: %d}}", &mympd_state.jukeboxQueueLength);
if (je == 1) {
snprintf(p_char, 4, "%d", mympd_state.jukeboxQueueLength);
if (!mympd_state_set("jukeboxQueueLength", p_char))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxQueueLength.\"}");
}
je = json_scanf(msg.p, msg.len, "{data: {random: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {random: %u}}", &uint_buf1);
if (je == 1)
if (!mpd_run_random(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state random.\"}");
je = json_scanf(msg.p, msg.len, "{data: {repeat: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {repeat: %u}}", &uint_buf1);
if (je == 1)
if (!mpd_run_repeat(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state repeat.\"}");
je = json_scanf(msg.p, msg.len, "{data: {consume: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {consume: %u}}", &uint_buf1);
if (je == 1)
if (!mpd_run_consume(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state consume.\"}");
je = json_scanf(msg.p, msg.len, "{data: {single: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {single: %u}}", &uint_buf1);
if (je == 1)
if (!mpd_run_single(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state single.\"}");
je = json_scanf(msg.p, msg.len, "{data: {crossfade: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {crossfade: %u}}", &uint_buf1);
if (je == 1)
if (!mpd_run_crossfade(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state crossfade.\"}");
if (config.mixramp) {
je = json_scanf(msg.p, msg.len, "{data: {mixrampdb: %f}}", &float_buf);
je = json_scanf(request->data, request->length, "{data: {mixrampdb: %f}}", &float_buf);
if (je == 1)
if (!mpd_run_mixrampdb(mpd.conn, float_buf))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state mixrampdb.\"}");
je = json_scanf(msg.p, msg.len, "{data: {mixrampdelay: %f}}", &float_buf);
je = json_scanf(request->data, request->length, "{data: {mixrampdelay: %f}}", &float_buf);
if (je == 1)
if (!mpd_run_mixrampdelay(mpd.conn, float_buf))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state mixrampdelay.\"}");
}
je = json_scanf(msg.p, msg.len, "{data: {replaygain: %Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {replaygain: %Q}}", &p_charbuf1);
if (je == 1) {
if (!mpd_send_command(mpd.conn, "replay_gain_mode", p_charbuf1, NULL))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set mpd state replaygain.\"}");
@ -287,11 +277,11 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Smart Playlists update failed\"}");
break;
case MPD_API_SMARTPLS_SAVE:
je = json_scanf(msg.p, msg.len, "{data: {type: %Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {type: %Q}}", &p_charbuf1);
n = 1;
if (je == 1) {
if (strcmp(p_charbuf1, "sticker") == 0) {
je = json_scanf(msg.p, msg.len, "{data: {playlist: %Q, sticker: %Q, maxentries: %d}}", &p_charbuf2, &p_charbuf3, &int_buf1);
je = json_scanf(request->data, request->length, "{data: {playlist: %Q, sticker: %Q, maxentries: %d}}", &p_charbuf2, &p_charbuf3, &int_buf1);
if (je == 3) {
n = mympd_smartpls_save(p_charbuf1, p_charbuf2, p_charbuf3, NULL, int_buf1, 0);
free(p_charbuf2);
@ -299,14 +289,14 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
}
else if (strcmp(p_charbuf1, "newest") == 0) {
je = json_scanf(msg.p, msg.len, "{data: {playlist: %Q, timerange: %d}}", &p_charbuf2, &int_buf1);
je = json_scanf(request->data, request->length, "{data: {playlist: %Q, timerange: %d}}", &p_charbuf2, &int_buf1);
if (je == 2) {
n = mympd_smartpls_save(p_charbuf1, p_charbuf2, NULL, NULL, 0, int_buf1);
free(p_charbuf2);
}
}
else if (strcmp(p_charbuf1, "search") == 0) {
je = json_scanf(msg.p, msg.len, "{data: {playlist: %Q, tag: %Q, searchstr: %Q}}", &p_charbuf2, &p_charbuf3, &p_charbuf4);
je = json_scanf(request->data, request->length, "{data: {playlist: %Q, tag: %Q, searchstr: %Q}}", &p_charbuf2, &p_charbuf3, &p_charbuf4);
if (je == 3) {
n = mympd_smartpls_save(p_charbuf1, p_charbuf2, p_charbuf3, p_charbuf4, 0, 0);
free(p_charbuf2);
@ -322,7 +312,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Saving playlist failed\"}");
break;
case MPD_API_SMARTPLS_GET:
je = json_scanf(msg.p, msg.len, "{data: {playlist: %Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {playlist: %Q}}", &p_charbuf1);
if (je == 1) {
n = mympd_smartpls_put(mpd.buf, p_charbuf1);
free(p_charbuf1);
@ -382,7 +372,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = mympd_queue_crop(mpd.buf);
break;
case MPD_API_QUEUE_RM_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {track:%u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {track:%u}}", &uint_buf1);
if (je == 1) {
if (mpd_run_delete_id(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -393,7 +383,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_RM_RANGE:
je = json_scanf(msg.p, msg.len, "{data: {start: %u, end: %u}}", &uint_buf1, &uint_buf2);
je = json_scanf(request->data, request->length, "{data: {start: %u, end: %u}}", &uint_buf1, &uint_buf2);
if (je == 2) {
if (mpd_run_delete_range(mpd.conn, uint_buf1, uint_buf2))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -404,7 +394,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_MOVE_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {from: %u, to: %u}}", &uint_buf1, &uint_buf2);
je = json_scanf(request->data, request->length, "{data: {from: %u, to: %u}}", &uint_buf1, &uint_buf2);
if (je == 2) {
uint_buf1--;
uint_buf2--;
@ -419,7 +409,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_MOVE_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {plist: %Q, from: %u, to: %u }}", &p_charbuf1, &uint_buf1, &uint_buf2);
je = json_scanf(request->data, request->length, "{data: {plist: %Q, from: %u, to: %u }}", &p_charbuf1, &uint_buf1, &uint_buf2);
if (je == 3) {
uint_buf1--;
uint_buf2--;
@ -437,7 +427,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYER_PLAY_TRACK:
je = json_scanf(msg.p, msg.len, "{data: { track:%u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: { track:%u}}", &uint_buf1);
if (je == 1) {
if (mpd_run_play_id(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -451,7 +441,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = mympd_put_outputs(mpd.buf);
break;
case MPD_API_PLAYER_TOGGLE_OUTPUT:
je = json_scanf(msg.p, msg.len, "{data: {output: %u, state: %u}}", &uint_buf1, &uint_buf2);
je = json_scanf(request->data, request->length, "{data: {output: %u, state: %u}}", &uint_buf1, &uint_buf2);
if (je == 2) {
if (uint_buf2) {
if (mpd_run_enable_output(mpd.conn, uint_buf1))
@ -472,7 +462,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYER_VOLUME_SET:
je = json_scanf(msg.p, msg.len, "{data: {volume:%u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {volume:%u}}", &uint_buf1);
if (je == 1) {
if (mpd_run_set_volume(mpd.conn, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -486,7 +476,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = mympd_put_volume(mpd.buf);
break;
case MPD_API_PLAYER_SEEK:
je = json_scanf(msg.p, msg.len, "{data: {songid: %u, seek: %u}}", &uint_buf1, &uint_buf2);
je = json_scanf(request->data, request->length, "{data: {songid: %u, seek: %u}}", &uint_buf1, &uint_buf2);
if (je == 2) {
if (mpd_run_seek_id(mpd.conn, uint_buf1, uint_buf2))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -497,13 +487,13 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_LIST:
je = json_scanf(msg.p, msg.len, "{data: {offset: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {offset: %u}}", &uint_buf1);
if (je == 1) {
n = mympd_put_queue(mpd.buf, uint_buf1, &mpd.queue_version, &mpd.queue_length);
}
break;
case MPD_API_QUEUE_LAST_PLAYED:
je = json_scanf(msg.p, msg.len, "{data: {offset: %u}}", &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {offset: %u}}", &uint_buf1);
if (je == 1) {
n = mympd_put_last_played_songs(mpd.buf, uint_buf1);
}
@ -512,14 +502,14 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
n = mympd_put_current_song(mpd.buf);
break;
case MPD_API_DATABASE_SONGDETAILS:
je = json_scanf(msg.p, msg.len, "{data: { uri: %Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: { uri: %Q}}", &p_charbuf1);
if (je == 1) {
n = mympd_put_songdetails(mpd.buf, p_charbuf1);
free(p_charbuf1);
}
break;
case MPD_API_DATABASE_TAG_LIST:
je = json_scanf(msg.p, msg.len, "{data: {offset: %u, filter: %Q, tag: %Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2);
je = json_scanf(request->data, request->length, "{data: {offset: %u, filter: %Q, tag: %Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2);
if (je == 3) {
n = mympd_put_db_tag(mpd.buf, uint_buf1, p_charbuf2, "", "", p_charbuf1);
free(p_charbuf1);
@ -527,7 +517,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_DATABASE_TAG_ALBUM_LIST:
je = json_scanf(msg.p, msg.len, "{data: {offset: %u, filter: %Q, search: %Q, tag: %Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2, &p_charbuf3);
je = json_scanf(request->data, request->length, "{data: {offset: %u, filter: %Q, search: %Q, tag: %Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2, &p_charbuf3);
if (je == 4) {
n = mympd_put_db_tag(mpd.buf, uint_buf1, "Album", p_charbuf3, p_charbuf2, p_charbuf1);
free(p_charbuf1);
@ -536,7 +526,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST:
je = json_scanf(msg.p, msg.len, "{data: {album: %Q, search: %Q, tag: %Q}}", &p_charbuf1, &p_charbuf2, &p_charbuf3);
je = json_scanf(request->data, request->length, "{data: {album: %Q, search: %Q, tag: %Q}}", &p_charbuf1, &p_charbuf2, &p_charbuf3);
if (je == 3) {
n = mympd_put_songs_in_album(mpd.buf, p_charbuf1, p_charbuf2, p_charbuf3);
free(p_charbuf1);
@ -545,7 +535,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_RENAME:
je = json_scanf(msg.p, msg.len, "{data: {from: %Q, to: %Q}}", &p_charbuf1, &p_charbuf2);
je = json_scanf(request->data, request->length, "{data: {from: %Q, to: %Q}}", &p_charbuf1, &p_charbuf2);
if (je == 2) {
//rename smart playlist
char old_pl_file[400];
@ -583,14 +573,14 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_LIST:
je = json_scanf(msg.p, msg.len, "{data: {offset: %u, filter: %Q}}", &uint_buf1, &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {offset: %u, filter: %Q}}", &uint_buf1, &p_charbuf1);
if (je == 2) {
n = mympd_put_playlists(mpd.buf, uint_buf1, p_charbuf1);
free(p_charbuf1);
}
break;
case MPD_API_PLAYLIST_CONTENT_LIST:
je = json_scanf(msg.p, msg.len, "{data: {uri: %Q, offset:%u, filter:%Q}}", &p_charbuf1, &uint_buf1, &p_charbuf2);
je = json_scanf(request->data, request->length, "{data: {uri: %Q, offset:%u, filter:%Q}}", &p_charbuf1, &uint_buf1, &p_charbuf2);
if (je == 3) {
n = mympd_put_playlist_list(mpd.buf, p_charbuf1, uint_buf1, p_charbuf2);
free(p_charbuf1);
@ -598,7 +588,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_ADD_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {plist:%Q, uri:%Q}}", &p_charbuf1, &p_charbuf2);
je = json_scanf(request->data, request->length, "{data: {plist:%Q, uri:%Q}}", &p_charbuf1, &p_charbuf2);
if (je == 2) {
if (mpd_run_playlist_add(mpd.conn, p_charbuf1, p_charbuf2))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Added %s to playlist %s\"}", p_charbuf2, p_charbuf1);
@ -611,7 +601,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_CLEAR:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q}}", &p_charbuf1);
if (je == 1) {
if (mpd_run_playlist_clear(mpd.conn, p_charbuf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -623,7 +613,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_RM_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q, track:%u}}", &p_charbuf1, &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q, track:%u}}", &p_charbuf1, &uint_buf1);
if (je == 2) {
if (mpd_run_playlist_delete(mpd.conn, p_charbuf1, uint_buf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -635,7 +625,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_DATABASE_FILESYSTEM_LIST:
je = json_scanf(msg.p, msg.len, "{data: {offset:%u, filter:%Q, path:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2);
je = json_scanf(request->data, request->length, "{data: {offset:%u, filter:%Q, path:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2);
if (je == 3) {
n = mympd_put_browse(mpd.buf, p_charbuf2, uint_buf1, p_charbuf1);
free(p_charbuf1);
@ -643,7 +633,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_ADD_TRACK_AFTER:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q, to:%d}}", &p_charbuf1, &int_buf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q, to:%d}}", &p_charbuf1, &int_buf1);
if (je == 2) {
int_rc = mpd_run_add_id_to(mpd.conn, p_charbuf1, int_buf1);
if (int_rc > -1 )
@ -656,7 +646,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_REPLACE_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q }}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q }}", &p_charbuf1);
if (je == 1) {
if (!mpd_run_clear(mpd.conn)) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Clearing queue failed.\"}");
@ -676,7 +666,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_ADD_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q}}", &p_charbuf1);
if (je == 1) {
if (mpd_run_add(mpd.conn, p_charbuf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -688,7 +678,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_ADD_PLAY_TRACK:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q}}", &p_charbuf1);
if (je == 1) {
int_buf1 = mpd_run_add_id(mpd.conn, p_charbuf1);
if (int_buf1 != -1) {
@ -707,7 +697,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_REPLACE_PLAYLIST:
je = json_scanf(msg.p, msg.len, "{data: {plist:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {plist:%Q}}", &p_charbuf1);
if (je == 1) {
if (!mpd_run_clear(mpd.conn)) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Clearing queue failed.\"}");
@ -727,7 +717,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_ADD_PLAYLIST:
je = json_scanf(msg.p, msg.len, "{data: {plist:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {plist:%Q}}", &p_charbuf1);
if (je == 1) {
if (mpd_run_load(mpd.conn, p_charbuf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -739,7 +729,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_SAVE:
je = json_scanf(msg.p, msg.len, "{ data: {plist:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{ data: {plist:%Q}}", &p_charbuf1);
if (je == 1) {
if (mpd_run_save(mpd.conn, p_charbuf1))
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -751,7 +741,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_QUEUE_SEARCH:
je = json_scanf(msg.p, msg.len, "{data: {offset:%u, filter:%Q, searchstr:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2);
je = json_scanf(request->data, request->length, "{data: {offset:%u, filter:%Q, searchstr:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2);
if (je == 3) {
n = mympd_search_queue(mpd.buf, p_charbuf1, uint_buf1, p_charbuf2);
free(p_charbuf1);
@ -759,7 +749,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_DATABASE_SEARCH:
je = json_scanf(msg.p, msg.len, "{data: {searchstr:%Q, filter:%Q, plist:%Q, offset:%u}}", &p_charbuf1, &p_charbuf2, &p_charbuf3, &uint_buf1);
je = json_scanf(request->data, request->length, "{data: {searchstr:%Q, filter:%Q, plist:%Q, offset:%u}}", &p_charbuf1, &p_charbuf2, &p_charbuf3, &uint_buf1);
if (je == 4) {
n = mympd_search(mpd.buf, p_charbuf1, p_charbuf2, p_charbuf3, uint_buf1);
free(p_charbuf1);
@ -767,7 +757,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_DATABASE_SEARCH_ADV:
je = json_scanf(msg.p, msg.len, "{data: {expression:%Q, sort:%Q, sortdesc:%B, plist:%Q, offset:%u}}",
je = json_scanf(request->data, request->length, "{data: {expression:%Q, sort:%Q, sortdesc:%B, plist:%Q, offset:%u}}",
&p_charbuf1, &p_charbuf2, &bool_buf, &p_charbuf3, &uint_buf1);
if (je == 5) {
n = mympd_search_adv(mpd.buf, p_charbuf1, p_charbuf2, bool_buf, NULL, p_charbuf3, uint_buf1);
@ -785,7 +775,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
}
break;
case MPD_API_PLAYLIST_RM:
je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1);
je = json_scanf(request->data, request->length, "{data: {uri:%Q}}", &p_charbuf1);
if (je == 1) {
//remove smart playlist
char pl_file[400];
@ -833,33 +823,31 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
fprintf(stderr, "DEBUG: Time used: %lu\n", delta_us);
#endif
if (n == 0)
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"No response for cmd %s\"}", cmd);
if (n == 0) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"No response for cmd %s.\"}", cmd);
}
LOG_DEBUG() fprintf(stderr, "DEBUG: Send http response to connection %lu (first 800 chars):\n%*.*s\n", request->conn_id, 0, 800, mpd.buf);
struct work_result_t *response = (struct work_result_t*)malloc(sizeof(struct work_result_t));
response->conn_id = request->conn_id;
response->length = copy_string(response->data, mpd.buf, MAX_SIZE, n);
tiny_queue_push(web_server_queue, response);
if (is_websocket(nc)) {
LOG_DEBUG() fprintf(stderr, "DEBUG: Send websocket response:\n %s\n", mpd.buf);
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, mpd.buf, n);
}
else {
LOG_DEBUG() fprintf(stderr, "DEBUG: Send http response (first 800 chars):\n%*.*s\n", 0, 800, mpd.buf);
mg_send_http_chunk(nc, mpd.buf, n);
}
free(cmd);
LOG_DEBUG() fprintf(stderr, "DEBUG: Entering mpd idle mode\n");
mpd_send_idle(mpd.conn);
free(request);
}
void mympd_notify(struct mg_mgr *s) {
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
if (!is_websocket(c))
continue;
mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, strlen(mpd.buf));
}
LOG_DEBUG() fprintf(stderr, "DEBUG: Websocket notify: %s\n", mpd.buf);
void mympd_notify(size_t len) {
LOG_DEBUG() fprintf(stderr, "DEBUG: Websocket notify: %s.\n", mpd.buf);
struct work_result_t *response = (struct work_result_t*)malloc(sizeof(struct work_result_t));
response->conn_id = 0;
response->length = copy_string(response->data, mpd.buf, MAX_SIZE, len);
tiny_queue_push(web_server_queue, response);
}
void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask) {
int len = 0;
void mympd_parse_idle(int idle_bitmask) {
size_t n = 0;
for (unsigned j = 0;; j++) {
enum mpd_idle idle_event = 1 << j;
const char *idle_name = mpd_idle_name(idle_event);
@ -869,19 +857,19 @@ void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask) {
LOG_VERBOSE() printf("MPD idle event: %s\n", idle_name);
switch(idle_event) {
case MPD_IDLE_DATABASE:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_database\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_database\"}");
mympd_smartpls_update_all();
break;
case MPD_IDLE_STORED_PLAYLIST:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_stored_playlist\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_stored_playlist\"}");
break;
case MPD_IDLE_QUEUE:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_queue\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_queue\"}");
if (mympd_state.jukeboxMode > 0)
mympd_jukebox();
break;
case MPD_IDLE_PLAYER:
len = mympd_put_state(mpd.buf, &mpd.song_id, &mpd.next_song_id, &mpd.last_song_id, &mpd.queue_version, &mpd.queue_length);
n = mympd_put_state(mpd.buf, &mpd.song_id, &mpd.next_song_id, &mpd.last_song_id, &mpd.queue_version, &mpd.queue_length);
if (mpd.song_id != mpd.last_song_id) {
if (mpd.last_last_played_id != mpd.song_id)
mympd_last_played_list(mpd.song_id);
@ -893,31 +881,32 @@ void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask) {
}
break;
case MPD_IDLE_MIXER:
len = mympd_put_volume(mpd.buf);
n = mympd_put_volume(mpd.buf);
break;
case MPD_IDLE_OUTPUT:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_outputs\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_outputs\"}");
break;
case MPD_IDLE_OPTIONS:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_options\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_options\"}");
break;
case MPD_IDLE_UPDATE:
len = mympd_get_updatedb_state(mpd.buf);
n = mympd_get_updatedb_state(mpd.buf);
break;
case MPD_IDLE_STICKER:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_sticker\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_sticker\"}");
break;
case MPD_IDLE_SUBSCRIPTION:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_subscription\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_subscription\"}");
break;
case MPD_IDLE_MESSAGE:
len = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_message\"}");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_message\"}");
break;
default:
len = 0;
n = 0;
}
if (n > 0) {
mympd_notify(n);
}
if (len > 0)
mympd_notify(s);
}
}
}
@ -1039,15 +1028,16 @@ void mympd_mpd_features() {
if (LIBMPDCLIENT_CHECK_VERSION(2, 17, 0) && mpd_connection_cmp_server_version(mpd.conn, 0, 21, 0) >= 0) {
mpd.feat_advsearch = true;
LOG_INFO() printf("Enabling advanced search\n");
LOG_INFO() printf("Enabling advanced search.\n");
}
else
LOG_INFO() printf("Disabling advanced search, depends on mpd >= 0.21.0 and libmpdclient >= 2.17.0\n");
LOG_INFO() printf("Disabling advanced search, depends on mpd >= 0.21.0 and libmpdclient >= 2.17.0.\n");
}
void mympd_idle(struct mg_mgr *s, int timeout) {
void mympd_idle(int timeout) {
struct pollfd fds[1];
int pollrc;
size_t n = 0;
switch (mpd.conn_state) {
case MPD_DISCONNECTED:
@ -1056,24 +1046,24 @@ void mympd_idle(struct mg_mgr *s, int timeout) {
mpd.conn = mpd_connection_new(config.mpdhost, config.mpdport, mpd.timeout);
if (mpd.conn == NULL) {
printf("MPD connection failed.");
snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"disconnected\"}");
mympd_notify(s);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"disconnected\"}");
mympd_notify(n);
mpd.conn_state = MPD_FAILURE;
return;
}
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
printf("MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"%s\"}", mpd_connection_get_error_message(mpd.conn));
mympd_notify(s);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"%s\"}", mpd_connection_get_error_message(mpd.conn));
mympd_notify(n);
mpd.conn_state = MPD_FAILURE;
return;
}
if (config.mpdpass && !mpd_run_password(mpd.conn, config.mpdpass)) {
printf("MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"%s\"}", mpd_connection_get_error_message(mpd.conn));
mympd_notify(s);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"%s\"}", mpd_connection_get_error_message(mpd.conn));
mympd_notify(n);
mpd.conn_state = MPD_FAILURE;
return;
}
@ -1090,8 +1080,8 @@ void mympd_idle(struct mg_mgr *s, int timeout) {
case MPD_FAILURE:
printf("MPD connection failed.\n");
snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"disconnected\"}");
mympd_notify(s);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"disconnected\"}");
mympd_notify(n);
case MPD_DISCONNECT:
case MPD_RECONNECT:
@ -1105,25 +1095,26 @@ void mympd_idle(struct mg_mgr *s, int timeout) {
fds[0].fd = mpd_connection_get_fd(mpd.conn);
fds[0].events = POLLIN;
pollrc = poll(fds, 1, timeout);
if (pollrc > 0 || idle_bitmask_save > 0) {
//Handle idle event
LOG_DEBUG() fprintf(stderr, "DEBUG: Leaving mpd idle mode\n");
unsigned mpd_client_queue_length = tiny_queue_length(mpd_client_queue);
if (pollrc > 0 || mpd_client_queue_length > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: Leaving mpd idle mode.\n");
mpd_send_noidle(mpd.conn);
if (pollrc > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: Checking for idle events\n");
//Handle idle events
LOG_DEBUG() fprintf(stderr, "DEBUG: Checking for idle events.\n");
enum mpd_idle idle_bitmask = mpd_recv_idle(mpd.conn, false);
mympd_parse_idle(s, idle_bitmask);
mympd_parse_idle(idle_bitmask);
}
else {
mpd_response_finish(mpd.conn);
}
if (idle_bitmask_save > 0) {
//Handle idle event saved in mympd_callback
LOG_DEBUG() fprintf(stderr, "DEBUG: Handle saved idle event\n");
mympd_parse_idle(s, idle_bitmask_save);
idle_bitmask_save = 0;
if (mpd_client_queue_length > 0) {
//Handle request
LOG_DEBUG() fprintf(stderr, "DEBUG: Handle request.\n");
struct work_request_t *req = tiny_queue_shift(mpd_client_queue);
mympd_api(req);
}
LOG_DEBUG() fprintf(stderr, "DEBUG: Entering mpd idle mode\n");
LOG_DEBUG() fprintf(stderr, "DEBUG: Entering mpd idle mode.\n");
mpd_send_idle(mpd.conn);
}
break;
@ -2647,7 +2638,7 @@ int mympd_put_stats(char *buffer) {
void mympd_disconnect() {
mpd.conn_state = MPD_DISCONNECT;
mympd_idle(NULL, 0);
mympd_idle(100);
}
int mympd_smartpls_put(char *buffer, char *playlist) {

View File

@ -26,7 +26,10 @@
#define __MPD_CLIENT_H__
#include "../dist/src/mongoose/mongoose.h"
#include "common.h"
#include "web_server.h"
#include "list.h"
#include "tiny_queue.h"
#define RETURN_ERROR_AND_RECOVER(X) do { \
printf("MPD %s: %s\n", X, mpd_connection_get_error_message(mpd.conn)); \
@ -65,13 +68,6 @@
} while (0)
#define LOG_INFO() if (config.loglevel >= 1)
#define LOG_VERBOSE() if (config.loglevel >= 2)
#define LOG_DEBUG() if (config.loglevel == 3)
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
#define MPD_CMDS(X) \
@ -153,7 +149,6 @@ struct t_mpd {
// Reponse Buffer
char buf[MAX_SIZE];
size_t buf_size;
// States
int song_id;
@ -181,38 +176,6 @@ struct list mympd_browsetags;
struct list last_played;
struct list syscmds;
typedef struct {
long mpdport;
const char *mpdhost;
const char *mpdpass;
const char *webport;
bool ssl;
const char *sslport;
const char *sslcert;
const char *sslkey;
const char *user;
bool coverimage;
const char *coverimagename;
long coverimagesize;
bool stickers;
bool mixramp;
const char *taglist;
const char *searchtaglist;
const char *browsetaglist;
bool smartpls;
const char *varlibdir;
const char *etcdir;
unsigned long max_elements_per_page;
bool syscmds;
bool localplayer;
long streamport;
const char *streamurl;
unsigned long last_played_count;
long loglevel;
} t_config;
t_config config;
typedef struct {
long playCount;
long skipCount;
@ -236,16 +199,13 @@ typedef struct {
} t_mympd_state;
t_mympd_state mympd_state;
static int is_websocket(const struct mg_connection *nc) {
return nc->flags & MG_F_IS_WEBSOCKET;
}
tiny_queue_t *mpd_client_queue;
int randrange(int n);
void mympd_idle(struct mg_mgr *sm, int timeout);
void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask);
void callback_mympd(struct mg_connection *nc, const struct mg_str msg);
void mympd_notify(struct mg_mgr *s);
void mympd_idle(int timeout);
void mympd_parse_idle(int idle_bitmask);
void mympd_api(struct work_request_t *request);
void mympd_notify(size_t n);
bool mympd_count_song_id(int song_id, char *name, int value);
bool mympd_count_song_uri(const char *uri, char *name, int value);
bool mympd_like_song_uri(const char *uri, int value);

View File

@ -30,97 +30,23 @@
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <pthread.h>
#include <mpd/client.h>
#include "../dist/src/mongoose/mongoose.h"
#include "../dist/src/frozen/frozen.h"
#include "../dist/src/inih/ini.h"
#include "common.h"
#include "mpd_client.h"
#include "web_server.h"
#include "config.h"
static sig_atomic_t s_signal_received = 0;
static struct mg_serve_http_opts s_http_server_opts;
static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler
s_signal_received = sig_num;
}
static void handle_api(struct mg_connection *nc, struct http_message *hm) {
if (!is_websocket(nc))
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n\r\n");
char buf[1000] = {0};
int len = sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len;
memcpy(buf, hm->body.p, len);
struct mg_str d = {buf, len};
callback_mympd(nc, d);
if (!is_websocket(nc))
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
}
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch(ev) {
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("New websocket request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
LOG_VERBOSE() printf("New Websocket connection established\n");
struct mg_str d = mg_mk_str("{\"cmd\":\"MPD_API_WELCOME\"}");
callback_mympd(nc, d);
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("HTTP request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0)
handle_api(nc, hm);
else
mg_serve_http(nc, hm, s_http_server_opts);
break;
}
case MG_EV_CLOSE: {
if (is_websocket(nc)) {
LOG_VERBOSE() printf("Websocket connection closed\n");
}
else {
LOG_VERBOSE() printf("HTTP connection closed\n");
}
break;
}
}
}
static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data) {
char *host;
char host_header[1024];
switch(ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
snprintf(host_header, 1024, "%.*s", host_hdr->len, host_hdr->p);
host = strtok(host_header, ":");
char s_redirect[250];
if (strcmp(config.sslport, "443") == 0)
snprintf(s_redirect, 250, "https://%s/", host);
else
snprintf(s_redirect, 250, "https://%s:%s/", host, config.sslport);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
}
}
static int inihandler(void* user, const char* section, const char* name, const char* value) {
t_config* p_config = (t_config*)user;
char *crap;
@ -373,6 +299,36 @@ bool testdir(char *name, char *dirname) {
}
}
void *mpd_client_thread(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
while (s_signal_received == 0) {
mympd_idle(100);
}
mympd_disconnect();
return NULL;
}
void *web_server_thread(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
while (s_signal_received == 0) {
mg_mgr_poll(mgr, 10);
unsigned web_server_queue_length = tiny_queue_length(web_server_queue);
if (web_server_queue_length > 0) {
struct work_result_t *response = tiny_queue_shift(web_server_queue);
if (response->conn_id == 0) {
//Websocket notify from mpd idle
send_ws_notify(mgr, response);
}
else {
//api response
send_api_response(mgr, response);
}
}
}
mg_mgr_free(mgr);
return NULL;
}
int main(int argc, char **argv) {
struct mg_mgr mgr;
struct mg_connection *nc;
@ -380,6 +336,8 @@ int main(int argc, char **argv) {
struct mg_bind_opts bind_opts;
const char *err;
char testdirname[400];
mpd_client_queue = tiny_queue_create();
web_server_queue = tiny_queue_create();
//defaults
config.mpdhost = "127.0.0.1";
@ -451,7 +409,7 @@ int main(int argc, char **argv) {
mg_mgr_init(&mgr, NULL);
if (config.ssl == true) {
nc_http = mg_bind(&mgr, config.webport, ev_handler_http);
nc_http = mg_bind(&mgr, config.webport, ev_handler_redirect);
if (nc_http == NULL) {
printf("Error listening on port %s\n", config.webport);
return EXIT_FAILURE;
@ -552,13 +510,25 @@ int main(int argc, char **argv) {
if (config.ssl == true)
LOG_INFO() printf("Listening on ssl port %s\n", config.sslport);
pthread_t mpd_client, web_server;
pthread_create(&mpd_client, NULL, mpd_client_thread, &mgr);
pthread_create(&web_server, NULL, web_server_thread, &mgr);
//Do nothing...
pthread_join(mpd_client, NULL);
pthread_join(web_server, NULL);
/*
mg_start_thread(mpd_client_thread, &mgr);
while (s_signal_received == 0) {
mg_mgr_poll(&mgr, 100);
mympd_idle(&mgr, 0);
}
mg_mgr_free(&mgr);
*/
list_free(&mpd_tags);
list_free(&mympd_tags);
mympd_disconnect();
return EXIT_SUCCESS;
}

80
src/tiny_queue.c Normal file
View File

@ -0,0 +1,80 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
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.
*/
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include "tiny_queue.h"
tiny_queue_t* tiny_queue_create() {
struct tiny_queue_t* queue = (struct tiny_queue_t*)malloc(sizeof(struct tiny_queue_t));
queue->head = NULL;
queue->tail = NULL;
queue->length = 0;
queue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
queue->wakeup = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
return queue;
}
void tiny_queue_push(tiny_queue_t *queue, void *data) {
pthread_mutex_lock(&queue->mutex);
struct tiny_msg_t* new_node = (struct tiny_msg_t*)malloc(sizeof(struct tiny_msg_t));
new_node->data = data;
new_node->next = NULL;
queue->length++;
if (queue->head == NULL && queue->tail == NULL){
queue->head = queue->tail = new_node;
}
else {
queue->tail->next = new_node;
queue->tail = new_node;
}
pthread_mutex_unlock(&queue->mutex);
pthread_cond_signal(&queue->wakeup);
}
int tiny_queue_length(tiny_queue_t *queue) {
pthread_mutex_lock(&queue->mutex);
unsigned len = queue->length;
pthread_mutex_unlock(&queue->mutex);
return len;
}
void *tiny_queue_shift(tiny_queue_t *queue) {
pthread_mutex_lock(&queue->mutex);
while (queue->head == NULL) {
// block if buffer is empty
pthread_cond_wait(&queue->wakeup, &queue->mutex);
}
struct tiny_msg_t* current_head = queue->head;
void *data = current_head->data;
if (queue->head == queue->tail) {
queue->head = queue->tail = NULL;
}
else {
queue->head = queue->head->next;
}
free(current_head);
queue->length--;
pthread_mutex_unlock(&queue->mutex);
return data;
}

View File

@ -2,11 +2,7 @@
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
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
@ -21,6 +17,24 @@
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __TINY_QUEUE_H__
#define __TINY_QUEUE_H__
void sanitize_string(const char *data);
int validate_path(char *path, const char *basepath);
typedef struct tiny_msg_t {
void *data;
struct tiny_msg_t *next;
} tiny_msg_t;
typedef struct tiny_queue_t {
unsigned length;
struct tiny_msg_t *head;
struct tiny_msg_t *tail;
pthread_mutex_t mutex;
pthread_cond_t wakeup;
} tiny_queue_t;
tiny_queue_t* tiny_queue_create();
void tiny_queue_push(struct tiny_queue_t *queue, void *data);
void *tiny_queue_shift(struct tiny_queue_t *queue);
int tiny_queue_length(struct tiny_queue_t *queue);
#endif

144
src/web_server.c Normal file
View File

@ -0,0 +1,144 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
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.
*/
#include "common.h"
#include "config.h"
#include "web_server.h"
#include "mpd_client.h"
static unsigned long s_next_id = 1;
int is_websocket(const struct mg_connection *nc) {
return nc->flags & MG_F_IS_WEBSOCKET;
}
void send_ws_notify(struct mg_mgr *mgr, struct work_result_t *response) {
struct mg_connection *c;
LOG_DEBUG() fprintf(stderr, "DEBUG: Got ws notify, broadcasting\n");
for (c = mg_next(mgr, NULL); c != NULL; c = mg_next(mgr, c)) {
if (!is_websocket(c))
continue;
mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, response->data, response->length);
}
free(response);
}
void send_api_response(struct mg_mgr *mgr, struct work_result_t *response) {
struct mg_connection *c;
LOG_DEBUG() fprintf(stderr, "DEBUG: Got API response for connection %lu.\n", response->conn_id);
for (c = mg_next(mgr, NULL); c != NULL; c = mg_next(mgr, c)) {
if (c->user_data != NULL) {
if ((unsigned long)c->user_data == response->conn_id) {
LOG_DEBUG() fprintf(stderr, "DEBUG: Sending to connection %lu: %s\n", (unsigned long)c->user_data, response->data);
mg_send_head(c, 200, response->length, "Content-Type: application/json");
mg_printf(c, "%s", response->data);
}
}
}
free(response);
}
void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
(void) nc;
(void) ev_data;
switch(ev) {
case MG_EV_ACCEPT: {
nc->user_data = (void *)++s_next_id;
LOG_DEBUG() fprintf(stderr, "DEBUG: New connection id %lu.\n", s_next_id);
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("New websocket request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
LOG_VERBOSE() printf("New Websocket connection established.\n");
char response[] = "{\"type\": \"welcome\", \"data\": {\"mympdVersion\": \"" MYMPD_VERSION "\"}}";
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, response, strlen(response));
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("HTTP request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0) {
struct work_request_t *request = (struct work_request_t*)malloc(sizeof(struct work_request_t));
request->conn_id = (unsigned long)nc->user_data;
request->length = copy_string(request->data, hm->body.p, 1000, hm->body.len);
//sizeof(request->data) - 1 < hm->body.len ? sizeof(request->data) - 1 : hm->body.len;
//memcpy(request->data, hm->body.p, request->length);
tiny_queue_push(mpd_client_queue, request);
}
else {
mg_serve_http(nc, hm, s_http_server_opts);
}
break;
}
case MG_EV_CLOSE: {
if (nc->user_data) {
LOG_VERBOSE() fprintf(stderr, "HTTP connection %lu closed.\n", (unsigned long)nc->user_data);
nc->user_data = NULL;
}
else {
LOG_VERBOSE() printf("HTTP connection closed.\n");
}
break;
}
default: {
break;
}
}
}
void ev_handler_redirect(struct mg_connection *nc_http, int ev, void *ev_data) {
char *host;
char host_header[1024];
switch(ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
snprintf(host_header, 1024, "%.*s", host_hdr->len, host_hdr->p);
host = strtok(host_header, ":");
char s_redirect[250];
if (strcmp(config.sslport, "443") == 0)
snprintf(s_redirect, 250, "https://%s/", host);
else
snprintf(s_redirect, 250, "https://%s:%s/", host, config.sslport);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
default: {
break;
}
}
}

52
src/web_server.h Normal file
View File

@ -0,0 +1,52 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
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 __WEB_SERVER_H__
#define __WEB_SERVER_H__
#include "../dist/src/mongoose/mongoose.h"
#include "tiny_queue.h"
tiny_queue_t *web_server_queue;
struct work_request_t {
unsigned long conn_id; // needed to identify the connection where to send the reply
char data[1000];
int length;
} work_request_t;
struct work_result_t {
unsigned long conn_id; // needed to identify the connection where to send the reply
char data[MAX_SIZE];
int length;
} work_result_t;
struct mg_serve_http_opts s_http_server_opts;
int is_websocket(const struct mg_connection *nc);
void ev_handler(struct mg_connection *nc, int ev, void *ev_data);
void ev_handler_redirect(struct mg_connection *nc_http, int ev, void *ev_data);
void send_ws_notify(struct mg_mgr *mgr, struct work_result_t *response);
void send_api_response(struct mg_mgr *mgr, struct work_result_t *response);
#endif