mirror of
https://github.com/SuperBFG7/ympd
synced 2024-09-15 00:19:39 +00:00
initial mongoose checkin
This commit is contained in:
parent
5920d9f1bf
commit
79e38e7edd
@ -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})
|
||||
|
@ -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
|
||||
)
|
@ -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 -->
|
||||
|
@ -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
87
htdocs/mkdata.pl
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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
4296
src/mongoose.c
Normal file
File diff suppressed because it is too large
Load Diff
125
src/mongoose.h
Normal file
125
src/mongoose.h
Normal 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
|
485
src/mpd_client.c
485
src/mpd_client.c
@ -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, ¤t_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);
|
||||
}
|
105
src/mpd_client.h
105
src/mpd_client.h
@ -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"
|