initial mongoose checkin

This commit is contained in:
Andrew Karpow 2014-02-16 19:46:53 +01:00
parent 5920d9f1bf
commit 79e38e7edd
13 changed files with 4938 additions and 676 deletions

View File

@ -5,27 +5,15 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_GENERATOR "DEB;RPM;TGZ")
set(CPACK_SOURCE_GENERATOR "TBZ2")
set(DEBIAN_PACKAGE_SECTION "web")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MPD web client based on Websockets and Bootstrap")
set(CPACK_PACKAGE_CONTACT "Andrew Karpow <andy@ympd.org>")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "andy@ndyk.de")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.0.0,libmpdclient2")
option(WITH_STATIC_WEBSOCKETS "Build with static libwebsockets library" ON)
option(WITH_MPD_HOST_CHANGE "Let users of the web frontend change the MPD Host" ON)
find_package(LibMPDClient REQUIRED)
find_package(LibWebSockets REQUIRED)
if(WITH_STATIC_WEBSOCKETS)
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
endif()
find_package(Threads REQUIRED)
configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in
${PROJECT_BINARY_DIR}/config.h)
include_directories(${PROJECT_BINARY_DIR} ${LIBWEBSOCKETS_INCLUDE_DIR})
include_directories(${PROJECT_BINARY_DIR})
include(CheckCSourceCompiles)
include(CPack)
@ -33,25 +21,20 @@ include(CPack)
set(CMAKE_C_FLAGS "-std=gnu99 -Wall")
set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic")
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/http_files.h
COMMAND perl mkdata.pl index.html js/* assets/* css/* fonts/* > ${PROJECT_BINARY_DIR}/http_files.h
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/htdocs
)
set(SOURCES
src/ympd.c
src/http_server.c
src/mpd_client.c
src/mongoose.c
)
add_executable(ympd ${SOURCES})
# TODO: use generator expressions introduced to CMake 2.8.12, too fresh yet
if(WITH_STATIC_WEBSOCKETS)
find_library(LIBWEBSOCKETS_LIBRARY_STATIC libwebsockets.a)
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY}
${LIBWEBSOCKETS_LIBRARY_STATIC} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES})
else()
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY}
${LIBWEBSOCKETS_LIBRARIES})
endif()
add_executable(ympd ${SOURCES} ${PROJECT_BINARY_DIR}/http_files.h)
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
install(TARGETS ympd DESTINATION bin)
install(FILES ympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)
install(DIRECTORY htdocs DESTINATION share/${PROJECT_NAME})

View File

@ -1,35 +0,0 @@
# This module tries to find libWebsockets library and include files
#
# LIBWEBSOCKETS_FOUND, If false, do not try to use libWebSockets
# LIBWEBSOCKETS_INCLUDE_DIR, path where to find libwebsockets.h
# LIBWEBSOCKETS_LIBRARY_DIR, path where to find libwebsockets.so
# LIBWEBSOCKETS_LIBRARIES, the library to link against
#
# This currently works probably only for Linux
find_package(PkgConfig)
pkg_check_modules(PC_LIBWEBSOCKETS QUIET libwebsockets)
set(LIBWEBSOCKETS_DEFINITIONS ${PC_LIBWEBSOCKETS_CFLAGS_OTHER})
find_path(LIBWEBSOCKETS_INCLUDE_DIR libwebsockets.h
HINTS ${PC_LIBWEBSOCKETS_INCLUDEDIR} ${PC_LIBWEBSOCKETS_INCLUDE_DIRS}
)
find_library(LIBWEBSOCKETS_LIBRARY websockets
HINTS ${PC_LIBWEBSOCKETS_LIBDIR} ${PC_LIBWEBSOCKETS_LIBRARY_DIRS}
)
set(LIBWEBSOCKETS_LIBRARIES ${LIBWEBSOCKETS_LIBRARY})
set(LIBWEBSOCKETS_INCLUDE_DIRS ${LIBWEBSOCKETS_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBWEBSOCKETS_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(LibWebSockets DEFAULT_MSG
LIBWEBSOCKETS_LIBRARY LIBWEBSOCKETS_INCLUDE_DIR
)
mark_as_advanced(
LIBWEBSOCKETS_LIBRARY
LIBWEBSOCKETS_INCLUDE_DIR
)

View File

@ -39,7 +39,7 @@
<ul id="nav_links" class="nav navbar-nav">
<li id="playlist"><a href="#/">Playlist</a></li>
<li id="browse"><a href="#/browse/">Browse database</a></li>
<li><a href="#" data-toggle="modal" data-target="#about" onclick="getVersion();">About</a></li>
<li><a href="#" data-toggle="modal" data-target="#about">About</a></li>
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
</ul>
@ -197,11 +197,23 @@
<input type="text" class="form-control" id="mpdport" />
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="control-label" for="mpd_pw">MPD Password</label>
<input type="password" class="form-control" id="mpd_pw" placeholder="Password"/>
</div>
<div class="form-group col-md-6">
<label class="control-label" for="mpd_pw_con">MPD Password (Confirmation)</label>
<input type="password" class="form-control" id="mpd_pw_con" placeholder="Password confirmation"
data-placement="right" data-toggle="popover" data-content="Password does not match!"
data-trigger="manual" />
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-default" data-dismiss="modal" onclick="setHost();">Save</button>
<button type="button" class="btn btn-default" onclick="confirmSettings();">Save</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->

View File

@ -41,7 +41,8 @@ var app = $.sammy(function() {
current_app = 'playlist';
$('#breadcrump').addClass('hide');
$('#salamisandwich').find("tr:gt(0)").remove();
$.get( "/api/get_playlist", socket.onmessage);
// $.get( "/api/get_playlist", socket.onmessage);
socket.send('MPD_API_GET_PLAYLIST');
$('#panel-heading').text("Playlist");
$('#playlist').addClass('active');
@ -53,7 +54,8 @@ var app = $.sammy(function() {
$('#salamisandwich').find("tr:gt(0)").remove();
var path = this.params['splat'][0];
$.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage);
// $.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage);
socket.send('MPD_API_GET_BROWSE,' + path);
$('#panel-heading').text("Browse database: "+path+"");
var path_array = path.split('/');
@ -100,13 +102,14 @@ $(document).ready(function(){
function webSocketConnect() {
if (typeof MozWebSocket != "undefined") {
socket = new MozWebSocket(get_appropriate_ws_url(), "ympd-client");
socket = new MozWebSocket(get_appropriate_ws_url());
} else {
socket = new WebSocket(get_appropriate_ws_url(), "ympd-client");
socket = new WebSocket(get_appropriate_ws_url());
}
try {
socket.onopen = function() {
console.log("connected");
$('.top-right').notify({
message:{text:"Connected to ympd"},
fadeOut: { enabled: true, delay: 500 }
@ -288,7 +291,7 @@ function webSocketConnect() {
break;
case "update_playlist":
if(current_app === 'playlist')
$.get( "/api/get_playlist", socket.onmessage);
socket.send('MPD_API_GET_PLAYLIST');
break;
case "song_change":
$('#currenttrack').text(" " + obj.data.title);
@ -315,6 +318,10 @@ function webSocketConnect() {
case "mpdhost":
$('#mpdhost').val(obj.data.host);
$('#mpdport').val(obj.data.port);
if(obj.data.passwort_set) {
$('#mpd_pw').attr('placeholder', '*******');
$('#mpd_pw_con').attr('placeholder', '*******');
}
break;
case "error":
$('.top-right').notify({
@ -328,6 +335,7 @@ function webSocketConnect() {
}
socket.onclose = function(){
console.log("disconnected");
$('.top-right').notify({
message:{text:"Connection to ympd lost, retrying in 3 seconds "},
type: "danger",
@ -448,30 +456,36 @@ $('#btnnotify').on('click', function (e) {
}
});
function getVersion()
{
$.get( "/api/get_version", function(response) {
$('#ympd_version').text(response.data.ympd_version);
$('#mpd_version').text(response.data.mpd_version);
});
}
function getHost() {
socket.send('MPD_API_GET_MPDHOST');
function onEnter(event) {
if ( event.which == 13 ) {
setHost();
$('#settings').modal('hide');
confirmSettings();
}
}
$('#mpdhost').keypress(onEnter);
$('#mpdport').keypress(onEnter);
$('#mpd_pw').keypress(onEnter);
$('#mpd_pw_con').keypress(onEnter);
}
function setHost() {
function confirmSettings() {
if($('#mpd_pw').val().length + $('#mpd_pw_con').val().length > 0) {
if ($('#mpd_pw').val() !== $('#mpd_pw_con').val())
{
$('#mpd_pw_con').popover('show');
setTimeout(function() {
$('#mpd_pw_con').popover('hide');
}, 2000);
return;
} else
socket.send('MPD_API_SET_MPDPASS,'+$('#mpd_pw').val());
}
socket.send('MPD_API_SET_MPDHOST,'+$('#mpdport').val()+','+$('#mpdhost').val());
$('#settings').modal('hide');
}
function notificationsSupported() {

87
htdocs/mkdata.pl Normal file
View File

@ -0,0 +1,87 @@
# This program is used to embed arbitrary data into a C binary. It takes
# a list of files as an input, and produces a .c data file that contains
# contents of all these files as collection of char arrays.
#
# Usage: perl <this_file> <file1> [file2, ...] > embedded_data.c
use File::MimeInfo;
foreach my $i (0 .. $#ARGV) {
open FD, '<:raw', $ARGV[$i] or die "Cannot open $ARGV[$i]: $!\n";
printf("static const unsigned char v%d[] = {", $i);
my $byte;
my $j = 0;
while (read(FD, $byte, 1)) {
if (($j % 12) == 0) {
print "\n";
}
printf ' %#04x,', ord($byte);
$j++;
}
print " 0x00\n};\n";
close FD;
}
print <<EOS;
/* ympd
(c) 2013-2014 Andrew Karpow <andy\@ympd.org>
This project's homepage is: http://www.ympd.org
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __HTTP_FILES_H__
#define __HTTP_FILES_H__
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
static const struct embedded_file {
const char *name;
const unsigned char *data;
const char *mimetype;
size_t size;
} embedded_files[] = {
EOS
foreach my $i (0 .. $#ARGV) {
my $mime = mimetype($ARGV[$i]);
print " {\"/$ARGV[$i]\", v$i, \"$mime\", sizeof(v$i) - 1},\n";
}
print <<EOS;
{NULL, NULL, NULL, 0}
};
const struct embedded_file *find_embedded_file(const char *name) {
const struct embedded_file *p;
for (p = embedded_files; p->name != NULL; p++)
if (!strcmp(p->name, name))
return p;
return NULL;
}
#endif
EOS

View File

@ -26,159 +26,29 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <libwebsockets.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <mpd/client.h>
#include "http_server.h"
#include "mpd_client.h"
#include "config.h"
#include "http_files.h"
char *resource_path = LOCAL_RESOURCE_PATH;
extern enum mpd_conn_states mpd_conn_state;
struct serveable {
const char *urlpath;
const char *mimetype;
};
static const struct serveable whitelist[] = {
{ "/css/bootstrap.css", "text/css" },
{ "/css/mpd.css", "text/css" },
{ "/js/bootstrap.min.js", "text/javascript" },
{ "/js/mpd.js", "text/javascript" },
{ "/js/jquery-1.10.2.min.js", "text/javascript" },
{ "/js/jquery.cookie.js", "text/javascript" },
{ "/js/bootstrap-slider.js", "text/javascript" },
{ "/js/bootstrap-notify.js", "text/javascript" },
{ "/js/sammy.js", "text/javascript" },
{ "/fonts/glyphicons-halflings-regular.woff", "application/x-font-woff"},
{ "/fonts/glyphicons-halflings-regular.svg", "image/svg+xml"},
{ "/fonts/glyphicons-halflings-regular.ttf", "application/x-font-ttf"},
{ "/fonts/glyphicons-halflings-regular.eot", "application/vnd.ms-fontobject"},
{ "/assets/favicon.ico", "image/vnd.microsoft.icon" },
/* last one is the default served if no match */
{ "/index.html", "text/html" },
};
static const char http_header[] = "HTTP/1.0 200 OK\x0d\x0a"
"Server: libwebsockets\x0d\x0a"
"Content-Type: application/json\x0d\x0a"
"Content-Length: 000000\x0d\x0a\x0d\x0a";
/* Converts a hex character to its integer value */
char from_hex(char ch) {
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}
/* Returns a url-decoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
char *url_decode(char *str) {
char *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;
while (*pstr) {
if (*pstr == '%') {
if (pstr[1] && pstr[2]) {
*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
pstr += 2;
}
} else if (*pstr == '+') {
*pbuf++ = ' ';
} else {
*pbuf++ = *pstr;
}
pstr++;
}
*pbuf = '\0';
return buf;
}
int callback_http(struct libwebsocket_context *context,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason, void *user,
void *in, size_t len)
int callback_http(struct mg_connection *c)
{
char *response_buffer, *p;
char buf[64];
size_t n, response_size = 0;
const struct embedded_file *req_file;
switch (reason) {
case LWS_CALLBACK_HTTP:
if(in && strncmp((const char *)in, "/api/", 5) == 0)
{
if(!strcmp(c->uri, "/"))
req_file = find_embedded_file("/index.html");
else
req_file = find_embedded_file(c->uri);
p = (char *)malloc(MAX_SIZE + 100);
memcpy(p, http_header, sizeof(http_header) - 1);
response_buffer = p + sizeof(http_header) - 1;
/* put content length and payload to buffer */
if(mpd_conn_state != MPD_CONNECTED) {}
else if(strncmp((const char *)in, "/api/get_browse", 15) == 0)
{
char *url;
if(sscanf(in, "/api/get_browse/%m[^\t\n]", &url) == 1)
{
char *url_decoded = url_decode(url);
response_size = mpd_put_browse(response_buffer, url_decoded);
free(url_decoded);
free(url);
}
else
response_size = mpd_put_browse(response_buffer, "/");
}
else if(strncmp((const char *)in, "/api/get_playlist", 17) == 0)
response_size = mpd_put_playlist(response_buffer);
else if(strncmp((const char *)in, "/api/get_version", 16) == 0)
response_size = snprintf(response_buffer, MAX_SIZE,
"{\"type\":\"version\",\"data\":{"
"\"ympd_version\":\"%d.%d.%d\","
"\"mpd_version\":\"%d.%d.%d\""
"}}",
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH,
LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION,
LIBMPDCLIENT_PATCH_VERSION);
/* Copy size to content-length field */
sprintf(buf, "%6zu", response_size);
memcpy(p + sizeof(http_header) - 11, buf, 6);
n = libwebsocket_write(wsi, (unsigned char *)p,
sizeof(http_header) - 1 + response_size, LWS_WRITE_HTTP);
free(p);
/*
* book us a LWS_CALLBACK_HTTP_WRITEABLE callback
*/
libwebsocket_callback_on_writable(context, wsi);
}
else
{
for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
break;
sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype, NULL))
return -1; /* through completion or error, close the socket */
}
break;
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
/* kill the connection after we sent one file */
return -1;
default:
break;
if(req_file)
{
mg_send_header(c, "Content-Type", req_file->mimetype);
mg_send_data(c, req_file->data, req_file->size);
return MG_REQUEST_PROCESSED;
}
return 0;
mg_send_status(c, 404);
mg_printf_data(c, "Not Found");
return MG_REQUEST_PROCESSED;
}

View File

@ -29,15 +29,9 @@
#ifndef __HTTP_SERVER_H__
#define __HTTP_SERVER_H__
#include <libwebsockets.h>
#include "mongoose.h"
struct per_session_data__http {
int fd;
};
int callback_http(struct libwebsocket_context *context,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason, void *user,
void *in, size_t len);
int callback_http(struct mg_connection *c);
#endif

4296
src/mongoose.c Normal file

File diff suppressed because it is too large Load Diff

125
src/mongoose.h Normal file
View File

@ -0,0 +1,125 @@
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
// Copyright (c) 2013-2014 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/>.
//
// NOTE: Detailed API documentation is at http://cesanta.com/#docs
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_VERSION "5.3"
#include <stdio.h> // required for FILE
#include <stddef.h> // required for size_t
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// This structure contains information about HTTP request.
struct mg_connection {
const char *request_method; // "GET", "POST", etc
const char *uri; // URL-decoded URI
const char *http_version; // E.g. "1.0", "1.1"
const char *query_string; // URL part after '?', not including '?', or NULL
char remote_ip[48]; // Max IPv6 string length is 45 characters
const char *local_ip; // Local IP address
unsigned short remote_port; // Client's port
unsigned short local_port; // Local port number
int num_headers; // Number of HTTP headers
struct mg_header {
const char *name; // HTTP header name
const char *value; // HTTP header value
} http_headers[30];
char *content; // POST (or websocket message) data, or NULL
size_t content_len; // content length
int is_websocket; // Connection is a websocket connection
int status_code; // HTTP status code for HTTP error handler
int wsbits; // First byte of the websocket frame
void *server_param; // Parameter passed to mg_add_uri_handler()
void *connection_param; // Placeholder for connection-specific data
void *callback_param; // Used by mg_iterate_over_connections()
};
struct mg_server; // Opaque structure describing server instance
typedef int (*mg_handler_t)(struct mg_connection *);
// Server management functions
struct mg_server *mg_create_server(void *server_param);
void mg_destroy_server(struct mg_server **);
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
unsigned int mg_poll_server(struct mg_server *, int milliseconds);
void mg_set_request_handler(struct mg_server *, mg_handler_t);
void mg_set_http_close_handler(struct mg_server *, mg_handler_t);
void mg_set_http_error_handler(struct mg_server *, mg_handler_t);
void mg_set_auth_handler(struct mg_server *, mg_handler_t);
const char **mg_get_valid_option_names(void);
const char *mg_get_option(const struct mg_server *server, const char *name);
void mg_set_listening_socket(struct mg_server *, int sock);
int mg_get_listening_socket(struct mg_server *);
void mg_iterate_over_connections(struct mg_server *, mg_handler_t, void *);
// Connection management functions
void mg_send_status(struct mg_connection *, int status_code);
void mg_send_header(struct mg_connection *, const char *name, const char *val);
void mg_send_data(struct mg_connection *, const void *data, int data_len);
void mg_printf_data(struct mg_connection *, const char *format, ...);
int mg_websocket_write(struct mg_connection *, int opcode,
const char *data, size_t data_len);
// Deprecated in favor of mg_send_* interface
int mg_write(struct mg_connection *, const void *buf, int len);
int mg_printf(struct mg_connection *conn, const char *fmt, ...);
const char *mg_get_header(const struct mg_connection *, const char *name);
const char *mg_get_mime_type(const char *name, const char *default_mime_type);
int mg_get_var(const struct mg_connection *conn, const char *var_name,
char *buf, size_t buf_len);
int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t);
int mg_parse_multipart(const char *buf, int buf_len,
char *var_name, int var_name_len,
char *file_name, int file_name_len,
const char **data, int *data_len);
// Utility functions
void *mg_start_thread(void *(*func)(void *), void *param);
char *mg_md5(char buf[33], ...);
int mg_authorize_digest(struct mg_connection *c, FILE *fp);
// Callback function return codes
enum { MG_REQUEST_NOT_PROCESSED, MG_REQUEST_PROCESSED, MG_REQUEST_CALL_AGAIN };
enum { MG_AUTH_FAIL, MG_AUTH_OK };
enum { MG_ERROR_NOT_PROCESSED, MG_ERROR_PROCESSED };
enum { MG_CLIENT_CONTINUE, MG_CLIENT_CLOSE };
// HTTP client events
enum {
MG_CONNECT_SUCCESS, MG_CONNECT_FAILURE,
MG_DOWNLOAD_SUCCESS, MG_DOWNLOAD_FAILURE
};
int mg_connect(struct mg_server *, const char *host, int port, int use_ssl,
mg_handler_t handler, void *param);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // MONGOOSE_HEADER_INCLUDED

View File

@ -31,254 +31,273 @@
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <mpd/client.h>
#include <mpd/status.h>
#include <mpd/song.h>
#include <mpd/entity.h>
#include <mpd/search.h>
#include <mpd/tag.h>
#include "mpd_client.h"
#include "config.h"
struct mpd_connection *conn = NULL;
enum mpd_conn_states mpd_conn_state = MPD_DISCONNECTED;
enum mpd_state mpd_play_state = MPD_STATE_UNKNOWN;
const char * mpd_cmd_strs[] = {
MPD_CMDS(GEN_STR)
};
int callback_ympd(struct libwebsocket_context *context,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason,
void *user, void *in, size_t len)
static inline enum mpd_cmd_ids get_cmd_id(char *cmd)
{
size_t n, m = -1;
char *buf = NULL, *p;
struct per_session_data__ympd *pss = (struct per_session_data__ympd *)user;
for(int i = 0; i < sizeof(mpd_cmd_strs)/sizeof(mpd_cmd_strs[0]); i++)
if(!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
return i;
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
lwsl_info("mpd_client: "
"LWS_CALLBACK_ESTABLISHED\n");
return -1;
}
int callback_mpd(struct mg_connection *c)
{
enum mpd_cmd_ids cmd_id = get_cmd_id(c->content);
size_t n = 0;
unsigned int uint_buf, uint_buf_2;
int int_buf;
char *p_charbuf;
if(cmd_id == -1)
return MG_CLIENT_CONTINUE;
if(mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST &&
cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS)
return MG_CLIENT_CONTINUE;
switch(cmd_id)
{
case MPD_API_UPDATE_DB:
mpd_run_update(mpd.conn, NULL);
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
buf = (char *)malloc(MAX_SIZE + LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING);
if(buf == NULL) {
lwsl_err("ERROR Failed allocating memory\n");
return -1;
}
p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
if(pss->do_send & DO_SEND_ERROR) {
n = snprintf(p, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}",
mpd_connection_get_error_message(conn));
pss->do_send &= ~DO_SEND_ERROR;
/* Try to recover error */
if (!mpd_connection_clear_error(conn))
mpd_conn_state = MPD_FAILURE;
}
else if(mpd_conn_state != MPD_CONNECTED) {
n = snprintf(p, MAX_SIZE, "{\"type\":\"disconnected\"}");
}
else if(pss->do_send & DO_SEND_PLAYLIST) {
/*n = mpd_put_playlist(p);*/
n = snprintf(p, MAX_SIZE, "{\"type\":\"update_playlist\"}");
pss->do_send &= ~DO_SEND_PLAYLIST;
}
else if(pss->do_send & DO_SEND_TRACK_INFO) {
n = mpd_put_current_song(p);
pss->do_send &= ~DO_SEND_TRACK_INFO;
}
else if(pss->do_send & DO_SEND_BROWSE) {
n = mpd_put_browse(p, pss->browse_path);
pss->do_send &= ~DO_SEND_BROWSE;
free(pss->browse_path);
}
else if(pss->do_send & DO_SEND_MPDHOST) {
n = snprintf(p, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": "
"{\"host\" : \"%s\", \"port\": \"%d\"}"
"}", mpd_host, mpd_port);
pss->do_send &= ~DO_SEND_MPDHOST;
}
else {
/* Default Action */
int current_song_id;
unsigned queue_version;
n = mpd_put_state(p, &current_song_id, &queue_version);
if(current_song_id != pss->current_song_id)
{
pss->current_song_id = current_song_id;
pss->do_send |= DO_SEND_TRACK_INFO;
libwebsocket_callback_on_writable(context, wsi);
}
else if(pss->queue_version != queue_version) {
pss->queue_version = queue_version;
pss->do_send |= DO_SEND_PLAYLIST;
libwebsocket_callback_on_writable(context, wsi);
}
}
if(n > 0)
m = libwebsocket_write(wsi, (unsigned char *)p, n, LWS_WRITE_TEXT);
if (m < n) {
lwsl_err("ERROR %d writing to socket\n", n, m);
free(buf);
return -1;
}
free(buf);
case MPD_API_SET_PAUSE:
mpd_run_toggle_pause(mpd.conn);
break;
case LWS_CALLBACK_RECEIVE:
if(!strcmp((const char *)in, MPD_API_GET_PLAYLIST))
pss->do_send |= DO_SEND_PLAYLIST;
else if(!strcmp((const char *)in, MPD_API_GET_TRACK_INFO))
pss->do_send |= DO_SEND_TRACK_INFO;
else if(!strcmp((const char *)in, MPD_API_UPDATE_DB))
mpd_run_update(conn, NULL);
else if(!strcmp((const char *)in, MPD_API_SET_PAUSE))
mpd_run_toggle_pause(conn);
else if(!strcmp((const char *)in, MPD_API_SET_PREV))
mpd_run_previous(conn);
else if(!strcmp((const char *)in, MPD_API_SET_NEXT))
mpd_run_next(conn);
else if(!strcmp((const char *)in, MPD_API_SET_PLAY))
mpd_run_play(conn);
else if(!strcmp((const char *)in, MPD_API_SET_STOP))
mpd_run_stop(conn);
else if(!strcmp((const char *)in, MPD_API_RM_ALL))
mpd_run_clear(conn);
else if(!strncmp((const char *)in, MPD_API_RM_TRACK, sizeof(MPD_API_RM_TRACK)-1)) {
unsigned id;
if(sscanf(in, "MPD_API_RM_TRACK,%u", &id))
mpd_run_delete_id(conn, id);
libwebsocket_callback_on_writable(context, wsi);
case MPD_API_SET_PREV:
mpd_run_previous(mpd.conn);
break;
case MPD_API_SET_NEXT:
mpd_run_next(mpd.conn);
break;
case MPD_API_SET_PLAY:
mpd_run_play(mpd.conn);
break;
case MPD_API_SET_STOP:
mpd_run_stop(mpd.conn);
break;
case MPD_API_RM_ALL:
mpd_run_clear(mpd.conn);
break;
case MPD_API_GET_PLAYLIST:
n = mpd_put_playlist(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);
break;
case MPD_API_PLAY_TRACK:
if(sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
mpd_run_play_id(mpd.conn, uint_buf);
break;
case MPD_API_TOGGLE_RANDOM:
if(sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf))
mpd_run_random(mpd.conn, uint_buf);
break;
case MPD_API_TOGGLE_REPEAT:
if(sscanf(c->content, "MPD_API_TOGGLE_REPEAT,%u", &uint_buf))
mpd_run_repeat(mpd.conn, uint_buf);
break;
case MPD_API_TOGGLE_CONSUME:
if(sscanf(c->content, "MPD_API_TOGGLE_CONSUME,%u", &uint_buf))
mpd_run_consume(mpd.conn, uint_buf);
break;
case MPD_API_TOGGLE_SINGLE:
if(sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf))
mpd_run_single(mpd.conn, uint_buf);
break;
case MPD_API_SET_VOLUME:
if(sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100)
mpd_run_set_volume(mpd.conn, uint_buf);
break;
case MPD_API_SET_SEEK:
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_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);
break;
case MPD_API_ADD_TRACK:
if(sscanf(c->content, "MPD_API_ADD_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
{
mpd_run_add(mpd.conn, p_charbuf);
free(p_charbuf);
}
else if(!strncmp((const char *)in, MPD_API_PLAY_TRACK, sizeof(MPD_API_PLAY_TRACK)-1)) {
unsigned id;
if(sscanf(in, "MPD_API_PLAY_TRACK,%u", &id))
mpd_run_play_id(conn, id);
}
else if(!strncmp((const char *)in, MPD_API_TOGGLE_RANDOM, sizeof(MPD_API_TOGGLE_RANDOM)-1)) {
unsigned random;
if(sscanf(in, "MPD_API_TOGGLE_RANDOM,%u", &random))
mpd_run_random(conn, random);
}
else if(!strncmp((const char *)in, MPD_API_TOGGLE_REPEAT, sizeof(MPD_API_TOGGLE_REPEAT)-1)) {
unsigned repeat;
if(sscanf(in, "MPD_API_TOGGLE_REPEAT,%u", &repeat))
mpd_run_repeat(conn, repeat);
}
else if(!strncmp((const char *)in, MPD_API_TOGGLE_CONSUME, sizeof(MPD_API_TOGGLE_CONSUME)-1)) {
unsigned consume;
if(sscanf(in, "MPD_API_TOGGLE_CONSUME,%u", &consume))
mpd_run_consume(conn, consume);
}
else if(!strncmp((const char *)in, MPD_API_TOGGLE_SINGLE, sizeof(MPD_API_TOGGLE_SINGLE)-1)) {
unsigned single;
if(sscanf(in, "MPD_API_TOGGLE_SINGLE,%u", &single))
mpd_run_single(conn, single);
}
else if(!strncmp((const char *)in, MPD_API_SET_VOLUME, sizeof(MPD_API_SET_VOLUME)-1)) {
unsigned int volume;
if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume <= 100)
mpd_run_set_volume(conn, volume);
}
else if(!strncmp((const char *)in, MPD_API_SET_SEEK, sizeof(MPD_API_SET_SEEK)-1)) {
unsigned int seek, songid;
if(sscanf(in, "MPD_API_SET_SEEK,%u,%u", &songid, &seek)) {
mpd_run_seek_id(conn, songid, seek);
}
}
else if(!strncmp((const char *)in, MPD_API_GET_BROWSE, sizeof(MPD_API_GET_BROWSE)-1)) {
char *dir;
if(sscanf(in, "MPD_API_GET_BROWSE,%m[^\t\n]", &dir) && dir != NULL) {
pss->do_send |= DO_SEND_BROWSE;
pss->browse_path = dir;
}
}
else if(!strncmp((const char *)in, MPD_API_ADD_TRACK, sizeof(MPD_API_ADD_TRACK)-1)) {
char *uri;
if(sscanf(in, "MPD_API_ADD_TRACK,%m[^\t\n]", &uri) && uri != NULL) {
mpd_run_add(conn, uri);
free(uri);
}
}
else if(!strncmp((const char *)in, MPD_API_ADD_PLAY_TRACK, sizeof(MPD_API_ADD_PLAY_TRACK)-1)) {
char *uri;
if(sscanf(in, "MPD_API_ADD_PLAY_TRACK,%m[^\t\n]", &uri) && uri != NULL) {
int added_song = mpd_run_add_id(conn, uri);
if(added_song != -1)
mpd_run_play_id(conn, added_song);
free(uri);
}
break;
case MPD_API_ADD_PLAY_TRACK:
if(sscanf(c->content, "MPD_API_ADD_PLAY_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
{
int_buf = mpd_run_add_id(mpd.conn, p_charbuf);
if(int_buf != -1)
mpd_run_play_id(mpd.conn, int_buf);
free(p_charbuf);
}
#ifdef WITH_MPD_HOST_CHANGE
else if(!strncmp((const char *)in, MPD_API_SET_MPDHOST, sizeof(MPD_API_SET_MPDHOST)-1)) {
char *host;
int port = 0;
if(sscanf(in, "MPD_API_SET_MPDHOST,%d,%m[^\t\n ]", &port, &host) && host != NULL && port > 0) {
strncpy(mpd_host, host, sizeof(mpd_host));
free(host);
mpd_port = port;
mpd_conn_state = MPD_RECONNECT;
break;
}
/* Commands allowed when disconnected from MPD server */
case MPD_API_SET_MPDHOST:
int_buf = 0;
if(sscanf(c->content, "MPD_API_SET_MPDHOST,%d,%m[^\t\n ]", &int_buf, &p_charbuf) &&
p_charbuf != NULL && int_buf > 0)
{
strncpy(mpd.host, p_charbuf, sizeof(mpd.host));
free(p_charbuf);
mpd.port = int_buf;
mpd.conn_state = MPD_RECONNECT;
return MG_CLIENT_CONTINUE;
}
else if(!strncmp((const char *)in, MPD_API_GET_MPDHOST, sizeof(MPD_API_GET_MPDHOST)-1)) {
pss->do_send |= DO_SEND_MPDHOST;
break;
case MPD_API_GET_MPDHOST:
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": "
"{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}"
"}", mpd.host, mpd.port, mpd.password ? "true" : "false");
printf("mpd_password is %p\n", mpd.password);
case MPD_API_SET_MPDPASS:
if(sscanf(c->content, "MPD_API_SET_MPDPASS,%m[^\t\n ]", &p_charbuf) &&
p_charbuf != NULL)
{
if(mpd.password)
free(mpd.password);
mpd.password = p_charbuf;
mpd.conn_state = MPD_RECONNECT;
printf("Got mpd pw %s\n", mpd.password);
return MG_CLIENT_CONTINUE;
}
break;
#endif
if(mpd_conn_state == MPD_CONNECTED && mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
pss->do_send |= DO_SEND_ERROR;
break;
default:
break;
}
if(mpd.conn_state == MPD_CONNECTED && mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS)
{
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}",
mpd_connection_get_error_message(mpd.conn));
/* Try to recover error */
if (!mpd_connection_clear_error(mpd.conn))
mpd.conn_state = MPD_FAILURE;
}
if(n > 0)
mg_websocket_write(c, 1, mpd.buf, n);
return MG_CLIENT_CONTINUE;
}
int mpd_close_handler(struct mg_connection *c)
{
/* Cleanup session data */
if(c->connection_param)
free(c->connection_param);
return 0;
}
void mpd_loop()
static int mpd_notify_callback(struct mg_connection *c) {
size_t n;
if(!c->is_websocket)
return MG_REQUEST_PROCESSED;
if(c->callback_param)
{
/* error message? */
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",
(const char *)c->callback_param);
mg_websocket_write(c, 1, mpd.buf, n);
return MG_REQUEST_PROCESSED;
}
if(!c->connection_param)
c->connection_param = calloc(1, sizeof(struct t_mpd_client_session));
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
if(mpd.conn_state != MPD_CONNECTED) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}");
mg_websocket_write(c, 1, mpd.buf, n);
}
else
{
mg_websocket_write(c, 1, mpd.buf, mpd.buf_size);
if(s->song_id != mpd.song_id)
{
n = mpd_put_current_song(mpd.buf);
mg_websocket_write(c, 1, mpd.buf, n);
s->song_id = mpd.song_id;
}
if(s->queue_version != mpd.queue_version)
{
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_playlist\"}");
mg_websocket_write(c, 1, mpd.buf, n);
s->queue_version = mpd.queue_version;
}
}
return MG_REQUEST_PROCESSED;
}
void mpd_poll(struct mg_server *s)
{
switch (mpd_conn_state) {
switch (mpd.conn_state) {
case MPD_DISCONNECTED:
/* Try to connect */
lwsl_notice("MPD Connecting to %s:%d\n", mpd_host, mpd_port);
conn = mpd_connection_new(mpd_host, mpd_port, 3000);
if (conn == NULL) {
lwsl_err("Out of memory.");
mpd_conn_state = MPD_FAILURE;
fprintf(stdout, "MPD Connecting to %s:%d\n", mpd.host, mpd.port);
mpd.conn = mpd_connection_new(mpd.host, mpd.port, 3000);
if (mpd.conn == NULL) {
fprintf(stderr, "Out of memory.");
mpd.conn_state = MPD_FAILURE;
return;
}
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) {
lwsl_err("MPD connection: %s\n", mpd_connection_get_error_message(conn));
mpd_conn_state = MPD_FAILURE;
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
mg_iterate_over_connections(s, mpd_notify_callback,
(void *)mpd_connection_get_error_message(mpd.conn));
mpd.conn_state = MPD_FAILURE;
return;
}
lwsl_notice("MPD connected.\n");
mpd_conn_state = MPD_CONNECTED;
if(mpd.password && !mpd_run_password(mpd.conn, mpd.password))
{
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
mg_iterate_over_connections(s, mpd_notify_callback,
(void *)mpd_connection_get_error_message(mpd.conn));
mpd.conn_state = MPD_FAILURE;
return;
}
fprintf(stderr, "MPD connected.\n");
mpd.conn_state = MPD_CONNECTED;
break;
case MPD_FAILURE:
lwsl_err("MPD connection failed.\n");
fprintf(stderr, "MPD connection failed.\n");
case MPD_DISCONNECT:
case MPD_RECONNECT:
if(conn != NULL)
mpd_connection_free(conn);
conn = NULL;
mpd_conn_state = MPD_DISCONNECTED;
if(mpd.conn != NULL)
mpd_connection_free(mpd.conn);
mpd.conn = NULL;
mpd.conn_state = MPD_DISCONNECTED;
break;
case MPD_CONNECTED:
/* Nothing to do */
mpd.buf_size = mpd_put_state(mpd.buf, &mpd.song_id, &mpd.queue_version);
mg_iterate_over_connections(s, mpd_notify_callback, NULL);
break;
}
}
@ -308,10 +327,10 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
struct mpd_status *status;
int len;
status = mpd_run_status(conn);
status = mpd_run_status(mpd.conn);
if (!status) {
lwsl_err("MPD mpd_run_status: %s\n", mpd_connection_get_error_message(conn));
mpd_conn_state = MPD_FAILURE;
fprintf(stderr, "MPD mpd_run_status: %s\n", mpd_connection_get_error_message(mpd.conn));
mpd.conn_state = MPD_FAILURE;
return 0;
}
@ -345,7 +364,7 @@ int mpd_put_current_song(char *buffer)
const char *end = buffer + MAX_SIZE;
struct mpd_song *song;
song = mpd_run_current_song(conn);
song = mpd_run_current_song(mpd.conn);
if(song == NULL)
return 0;
@ -362,7 +381,7 @@ int mpd_put_current_song(char *buffer)
cur += snprintf(cur, end - cur, "}}");
mpd_song_free(song);
mpd_response_finish(conn);
mpd_response_finish(mpd.conn);
return cur - buffer;
}
@ -373,19 +392,19 @@ int mpd_put_playlist(char *buffer)
const char *end = buffer + MAX_SIZE;
struct mpd_entity *entity;
if (!mpd_send_list_queue_meta(conn)) {
lwsl_err("MPD mpd_send_list_queue_meta: %s\n", mpd_connection_get_error_message(conn));
if (!mpd_send_list_queue_meta(mpd.conn)) {
fprintf(stderr, "MPD mpd_send_list_queue_meta: %s\n", mpd_connection_get_error_message(mpd.conn));
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}",
mpd_connection_get_error_message(conn));
mpd_connection_get_error_message(mpd.conn));
if (!mpd_connection_clear_error(conn))
mpd_conn_state = MPD_FAILURE;
if (!mpd_connection_clear_error(mpd.conn))
mpd.conn_state = MPD_FAILURE;
return cur - buffer;
}
cur += snprintf(cur, end - cur, "{\"type\": \"playlist\", \"data\": [ ");
while((entity = mpd_recv_entity(conn)) != NULL) {
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song;
if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
@ -413,18 +432,18 @@ int mpd_put_browse(char *buffer, char *path)
const char *end = buffer + MAX_SIZE;
struct mpd_entity *entity;
if (!mpd_send_list_meta(conn, path)) {
lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn));
if (!mpd_send_list_meta(mpd.conn, path)) {
fprintf(stderr, "MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(mpd.conn));
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}",
mpd_connection_get_error_message(conn));
mpd_connection_get_error_message(mpd.conn));
if (!mpd_connection_clear_error(conn))
mpd_conn_state = MPD_FAILURE;
if (!mpd_connection_clear_error(mpd.conn))
mpd.conn_state = MPD_FAILURE;
return cur - buffer;
}
cur += snprintf(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
while((entity = mpd_recv_entity(conn)) != NULL) {
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song;
const struct mpd_directory *dir;
const struct mpd_playlist *pl;
@ -463,9 +482,9 @@ int mpd_put_browse(char *buffer, char *path)
mpd_entity_free(entity);
}
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(conn)) {
lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn));
mpd_conn_state = MPD_FAILURE;
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
fprintf(stderr, "MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(mpd.conn));
mpd.conn_state = MPD_FAILURE;
return 0;
}
@ -474,3 +493,9 @@ int mpd_put_browse(char *buffer, char *path)
cur += snprintf(cur, end - cur, "] }");
return cur - buffer;
}
void mpd_disconnect()
{
mpd.conn_state = MPD_DISCONNECT;
mpd_poll(NULL);
}

View File

@ -29,68 +29,75 @@
#ifndef __MPD_CLIENT_H__
#define __MPD_CLIENT_H__
#include <libwebsockets.h>
#include "mongoose.h"
#define MAX_SIZE 1024 * 100
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
#define MPD_CMDS(X) \
X(MPD_API_GET_PLAYLIST) \
X(MPD_API_GET_BROWSE) \
X(MPD_API_GET_MPDHOST) \
X(MPD_API_ADD_TRACK) \
X(MPD_API_ADD_PLAY_TRACK) \
X(MPD_API_PLAY_TRACK) \
X(MPD_API_RM_TRACK) \
X(MPD_API_RM_ALL) \
X(MPD_API_SET_VOLUME) \
X(MPD_API_SET_PAUSE) \
X(MPD_API_SET_PLAY) \
X(MPD_API_SET_STOP) \
X(MPD_API_SET_SEEK) \
X(MPD_API_SET_NEXT) \
X(MPD_API_SET_PREV) \
X(MPD_API_SET_MPDHOST) \
X(MPD_API_SET_MPDPASS) \
X(MPD_API_UPDATE_DB) \
X(MPD_API_TOGGLE_RANDOM) \
X(MPD_API_TOGGLE_CONSUME) \
X(MPD_API_TOGGLE_SINGLE) \
X(MPD_API_TOGGLE_REPEAT)
#define DO_SEND_STATE (1 << 0)
#define DO_SEND_PLAYLIST (1 << 1)
#define DO_SEND_TRACK_INFO (1 << 2)
#define DO_SEND_BROWSE (1 << 3)
#define DO_SEND_ERROR (1 << 4)
#define DO_SEND_MPDHOST (1 << 5)
#define MPD_API_GET_SEEK "MPD_API_GET_SEEK"
#define MPD_API_GET_PLAYLIST "MPD_API_GET_PLAYLIST"
#define MPD_API_GET_TRACK_INFO "MPD_API_GET_TRACK_INFO"
#define MPD_API_GET_BROWSE "MPD_API_GET_BROWSE"
#define MPD_API_GET_MPDHOST "MPD_API_GET_MPDHOST"
#define MPD_API_ADD_TRACK "MPD_API_ADD_TRACK"
#define MPD_API_ADD_PLAY_TRACK "MPD_API_ADD_PLAY_TRACK"
#define MPD_API_PLAY_TRACK "MPD_API_PLAY_TRACK"
#define MPD_API_RM_TRACK "MPD_API_RM_TRACK"
#define MPD_API_RM_ALL "MPD_API_RM_ALL"
#define MPD_API_SET_VOLUME "MPD_API_SET_VOLUME"
#define MPD_API_SET_PAUSE "MPD_API_SET_PAUSE"
#define MPD_API_SET_PLAY "MPD_API_SET_PLAY"
#define MPD_API_SET_STOP "MPD_API_SET_STOP"
#define MPD_API_SET_SEEK "MPD_API_SET_SEEK"
#define MPD_API_SET_NEXT "MPD_API_SET_PREV"
#define MPD_API_SET_PREV "MPD_API_SET_NEXT"
#define MPD_API_SET_MPDHOST "MPD_API_SET_MPDHOST"
#define MPD_API_UPDATE_DB "MPD_API_UPDATE_DB"
#define MPD_API_TOGGLE_RANDOM "MPD_API_TOGGLE_RANDOM"
#define MPD_API_TOGGLE_CONSUME "MPD_API_TOGGLE_CONSUME"
#define MPD_API_TOGGLE_SINGLE "MPD_API_TOGGLE_SINGLE"
#define MPD_API_TOGGLE_REPEAT "MPD_API_TOGGLE_REPEAT"
struct per_session_data__ympd {
int do_send;
unsigned queue_version;
int current_song_id;
char *browse_path;
enum mpd_cmd_ids {
MPD_CMDS(GEN_ENUM)
};
enum mpd_conn_states {
MPD_FAILURE,
MPD_DISCONNECTED,
MPD_FAILURE,
MPD_CONNECTED,
MPD_RECONNECT
MPD_RECONNECT,
MPD_DISCONNECT
};
void *mpd_idle_connection(void *_data);
int callback_ympd(struct libwebsocket_context *context,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason,
void *user, void *in, size_t len);
void mpd_loop();
struct t_mpd {
int port;
char host[128];
char *password;
struct mpd_connection *conn;
enum mpd_conn_states conn_state;
/* Reponse Buffer */
char buf[MAX_SIZE];
size_t buf_size;
int song_id;
unsigned queue_version;
} mpd;
struct t_mpd_client_session {
int song_id;
unsigned queue_version;
};
void mpd_poll(struct mg_server *s);
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_playlist(char *buffer);
int mpd_put_browse(char *buffer, char *path);
int mpd_port;
char mpd_host[255];
void mpd_disconnect();
#endif

View File

@ -1,40 +0,0 @@
/*
from http://www.geekhideout.com/urlcode.shtml
public domain
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
/* Converts a hex character to its integer value */
char from_hex(char ch) {
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}
/* Converts an integer value to its hex character*/
char to_hex(char code) {
static char hex[] = "0123456789abcdef";
return hex[code & 15];
}
/* Returns a url-decoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
char *url_decode(char *str) {
char *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;
while (*pstr) {
if (*pstr == '%') {
if (pstr[1] && pstr[2]) {
*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
pstr += 2;
}
} else if (*pstr == '+') {
*pbuf++ = ' ';
} else {
*pbuf++ = *pstr;
}
pstr++;
}
*pbuf = '\0';
return buf;
}

View File

@ -26,39 +26,20 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <libwebsockets.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <pthread.h>
#include "mongoose.h"
#include "http_server.h"
#include "mpd_client.h"
#include "config.h"
extern char *optarg;
extern char *resource_path;
struct libwebsocket_protocols protocols[] = {
/* first protocol must always be HTTP handler */
{
"http-only", /* name */
callback_http, /* callback */
sizeof (struct per_session_data__http), /* per_session_data_size */
0, /* max frame size / rx buffer */
},
{
"ympd-client",
callback_ympd,
sizeof(struct per_session_data__ympd),
255,
0,
},
{ NULL, NULL, 0, 0, 0 } /* terminator */
};
int force_exit = 0;
@ -67,80 +48,62 @@ void bye()
force_exit = 1;
}
static int server_callback(struct mg_connection *c) {
//printf("Got REQ: (%lu) WS:%d\n", c->content_len, c->is_websocket);
//fwrite(c->content, c->content_len, 1, stdout);
//printf("\n");
if (c->is_websocket)
{
c->content[c->content_len] = '\0';
if(c->content_len)
return callback_mpd(c);
else
return MG_CLIENT_CONTINUE;
}
else
return callback_http(c);
}
int main(int argc, char **argv)
{
int n, gid = -1, uid = -1;
int option_index = 0;
unsigned int oldus = 0;
struct libwebsocket_context *context;
struct lws_context_creation_info info;
const char *cert_filepath = NULL;
const char *private_key_filepath = NULL;
const char *iface = NULL;
int n, option_index = 0;
struct mg_server *server = mg_create_server(NULL);
unsigned int current_timer = 0, last_timer = 0;
atexit(bye);
memset(&info, 0, sizeof info);
info.port = 8080;
strcpy(mpd_host, "127.0.0.1");
mpd_port = 6600;
lws_set_log_level(LLL_ERR | LLL_WARN, NULL);
mg_set_option(server, "listening_port", "8080");
mpd.port = 6600;
strcpy(mpd.host, "127.0.0.1");
static struct option long_options[] = {
{"host", required_argument, 0, 'h'},
{"port", required_argument, 0, 'p'},
{"interface", required_argument, 0, 'i'},
{"webport", required_argument, 0, 'w'},
{"resourcepath", required_argument, 0, 'r'},
{"ssl_cert", required_argument, 0, 'c'},
{"ssl_key", required_argument, 0, 'k'},
{"gid", required_argument, 0, 'g'},
{"uid", required_argument, 0, 'u'},
{"verbose", optional_argument, 0, 'v'},
{"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'V'},
{"help", no_argument, 0, 0 },
{0, 0, 0, 0 }
};
while((n = getopt_long(argc, argv, "h:p:i:w:r:c:k:g:u:v::V",
while((n = getopt_long(argc, argv, "h:p:w:u::V",
long_options, &option_index)) != -1) {
switch (n) {
case 'h':
strncpy(mpd_host, optarg, sizeof(mpd_host));
strncpy(mpd.host, optarg, sizeof(mpd.host));
break;
case 'p':
mpd_port = atoi(optarg);
case 'i':
iface = optarg;
break;
mpd.port = atoi(optarg);
case 'w':
info.port = atoi(optarg);
break;
case 'r':
resource_path = optarg;
break;
case 'c':
cert_filepath = optarg;
break;
case 'k':
private_key_filepath = optarg;
break;
case 'g':
gid = atoi(optarg);
mg_set_option(server, "listening_port", optarg);
break;
case 'u':
uid = atoi(optarg);
break;
case 'v':
if(optarg)
lws_set_log_level(strtol(optarg, NULL, 10), NULL);
else
lws_set_log_level(LLL_ERR | LLL_WARN |
LLL_NOTICE | LLL_INFO, NULL);
mg_set_option(server, "run_as_user", optarg);
break;
case 'V':
fprintf(stdout, "ympd %d.%d.%d\n"
"Copyright (C) 2014 Andrew Karpow <andy@ympd.org>\n"
"Resource Path: "LOCAL_RESOURCE_PATH"\n"
"Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH);
return EXIT_SUCCESS;
@ -149,14 +112,8 @@ int main(int argc, char **argv)
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-i, --interface <interface>\tlisten interface for webserver [all]\n"
"\t-w, --webport <port>\t\tlisten port for webserver [8080]\n"
"\t-r, --resourcepath <path>\tresourcepath for webserver [" LOCAL_RESOURCE_PATH "]\n"
"\t-c, --ssl_cert <filepath>\tssl certificate ssl_private_key_filepath\n"
"\t-k, --ssl_key <filepath>\tssl private key filepath\n"
"\t-u, --uid <id>\t\t\tuser-id after socket bind\n"
"\t-g, --gid <id>\t\t\tgroup-id after socket bind\n"
"\t-v, --verbose[<level>]\t\tverbosity level\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"
, argv[0]);
@ -164,52 +121,19 @@ int main(int argc, char **argv)
}
}
if(cert_filepath != NULL && private_key_filepath == NULL) {
lwsl_err("private key filepath needed\n");
return EXIT_FAILURE;
}
if(private_key_filepath != NULL && cert_filepath == NULL) {
lwsl_err("public cert filepath needed\n");
return EXIT_FAILURE;
}
info.ssl_cert_filepath = cert_filepath;
info.ssl_private_key_filepath = private_key_filepath;
info.iface = iface;
info.protocols = protocols;
info.extensions = libwebsocket_get_internal_extensions();
info.gid = gid;
info.uid = uid;
info.options = 0;
context = libwebsocket_create_context(&info);
if (context == NULL) {
lwsl_err("libwebsocket init failed\n");
return EXIT_FAILURE;
}
n = 0;
while (n >= 0 && !force_exit) {
struct timeval tv;
gettimeofday(&tv, NULL);
/*
* This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
* live websocket connection using the DUMB_INCREMENT protocol,
* as soon as it can take more packets (usually immediately)
*/
if (((unsigned int)tv.tv_usec - oldus) > 1000 * 500) {
mpd_loop();
libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
oldus = tv.tv_usec;
mg_set_http_close_handler(server, mpd_close_handler);
mg_set_request_handler(server, server_callback);
while (!force_exit) {
current_timer = mg_poll_server(server, 200);
if(current_timer - last_timer)
{
last_timer = current_timer;
mpd_poll(server);
}
n = libwebsocket_service(context, 50);
}
libwebsocket_context_destroy(context);
return 0;
mpd_disconnect();
mg_destroy_server(&server);
return EXIT_SUCCESS;
}