Compare commits

..

No commits in common. "master" and "v1.2.2" have entirely different histories.

37 changed files with 2494 additions and 15590 deletions

View File

@ -1,168 +0,0 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: true
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: '^<.*'
Priority: 2
SortPriority: 0
- Regex: '.*'
Priority: 3
SortPriority: 0
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
---

17
.drone.yml Normal file
View File

@ -0,0 +1,17 @@
image: gcc4.8
script:
- sudo apt-get install -y libmpdclient-dev &> /dev/null
- cmake . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=Debug
- make VERBOSE=1
notify:
email:
recipients:
- andy@ndyk.de
irc:
server: irc.freenode.org
nick: droneBot
channel: '#nicotest'
on_started: true
on_success: true
on_failure: true

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
# Build Artifacts
build/

View File

@ -1,13 +0,0 @@
# Used by Prettier to ignore certain files and folders completely.
# See https://prettier.io/docs/en/ignore.html for details.
# Ignore build and system artifacts
build/
cmake/
contrib/
# Ignore all 3rd party libraries
**/bootstrap*
**/jquery*
**/modernizr*
**/sammy*

View File

@ -1,20 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"overrides": [
{
"files": ["*.html"],
"options": {
"tabWidth": 2
}
},
{
"files": ["*.css"],
"options": {
"tabWidth": 2
}
}
]
}

View File

@ -1,17 +0,0 @@
language: c
sudo: required
dist: trusty
compiler:
- gcc
- clang
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libmpdclient-dev cmake
- mkdir build
- cd build
- cmake -D CMAKE_BUILD_TYPE=DEBUG ..
script: make

View File

@ -1,80 +1,53 @@
cmake_minimum_required(VERSION 2.6)
project (ympd C)
project (ympd)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "2")
set(CPACK_PACKAGE_VERSION_PATCH "3")
if(CMAKE_BUILD_TYPE MATCHES RELEASE)
set(ASSETS_PATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/htdocs")
else()
set(ASSETS_PATH "${PROJECT_SOURCE_DIR}/htdocs")
endif()
set(CPACK_PACKAGE_VERSION_PATCH "1")
option(WITH_MPD_HOST_CHANGE "Let users of the web frontend change the MPD Host" ON)
option(WITH_DYNAMIC_ASSETS "Serve assets dynamically (e.g for development/packaging)" OFF)
option(WITH_IPV6 "enable IPv6 support" ON)
option(WITH_SSL "enable SSL support" ON)
find_package(LibMPDClient REQUIRED)
find_package(Threads REQUIRED)
configure_file(src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_INCLUDE_DIR})
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR})
include(CheckCSourceCompiles)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -pedantic")
if(WITH_IPV6)
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_IPV6)
endif()
if(WITH_SSL)
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
# list(APPEND LIB_LIST ${OPENSSL_LIBRARIES})
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_SSL)
endif()
set(CMAKE_C_FLAGS "-std=gnu99 -Wall")
set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic")
file(GLOB RESOURCES
RELATIVE ${PROJECT_SOURCE_DIR}
htdocs/js/*
htdocs/assets/*
htdocs/css/*.css
htdocs/fonts/*
htdocs/index.html
htdocs/player.html
RELATIVE ${PROJECT_SOURCE_DIR}
htdocs/js/*
htdocs/assets/*
htdocs/css/*.min.css
htdocs/fonts/*
htdocs/index.html
)
add_executable(mkdata htdocs/mkdata.c)
get_target_property(MKDATA_EXE mkdata LOCATION)
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/assets.c
COMMAND ${MKDATA_EXE} ${RESOURCES} > ${PROJECT_BINARY_DIR}/assets.c
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
DEPENDS ${RESOURCES} mkdata
)
set(SOURCES
src/ympd.c
src/http_server.c
src/mpd_client.c
src/mongoose.c
src/json_encode.c
assets.c
)
if(NOT WITH_DYNAMIC_ASSETS)
if(CMAKE_CROSSCOMPILING)
set(MKDATA_EXE ${PROJECT_SOURCE_DIR}/tools/mkdata.pl)
else()
set(MKDATA_EXE $<TARGET_FILE:mkdata>)
set(MKDATA_TARGET mkdata)
add_executable(mkdata tools/mkdata.c)
endif()
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/assets.c
COMMAND ${MKDATA_EXE} ${RESOURCES} > ${PROJECT_BINARY_DIR}/assets.c
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
DEPENDS ${RESOURCES} ${MKDATA_TARGET}
)
list(APPEND SOURCES src/http_server.c assets.c)
endif()
add_executable(ympd ${SOURCES})
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES})
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)
if(WITH_DYNAMIC_ASSETS)
install(DIRECTORY htdocs DESTINATION share/${PROJECT_NAME})
endif()

View File

@ -1,32 +0,0 @@
# Development Notes
## Code Formatting
The project has been formatted with [prettier.io](https://prettier.io/) for the HTML, JavaScript, CSS, and Markdown files. See the configuration file [.prettierrc.json](./.prettierrc.json) and the ignore file [.prettierignore](./.prettierignore) for details. If `prettier` is installed globally, there's no need to provide the various `npm`-type dependencies in the project. Various editors may provide plugins that can use this configuration without having to install `npm` and `prettier` manually.
Manual Usage:
```bash
> npx prettier --write .
```
The C source and header files have been formatted with `clang-format`. There's no easy way to manually execute the formatter on all of the C files at the same time. The clang format is based off of the 'Google' style with ajdustments to make the changes not as disruptive. See [.clang-format](./.clang-format) file for the formatting rules. Various editors should be able to automatically format the source on save.
The only files formatted are the non-third party library files.
Manual Usage:
```bash
> clang-format -i -style=file <filename>
```
Manually formatted files:
- http_server.c
- http_server.h
- json_encode.h
- mpd_client.c
- mpd_client.h
- ympd.c
For help with the rules, see [Clang Format Configurator](https://zed0.co.uk/clang-format-configurator/) for an interactive tool and [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) for the rules reference.

View File

@ -1,13 +0,0 @@
FROM alpine:3.5
WORKDIR /app/build
COPY . /app
RUN apk add --no-cache g++ make cmake libmpdclient-dev openssl-dev
RUN cmake ..
RUN make
FROM alpine:3.5
RUN apk add --no-cache libmpdclient openssl
EXPOSE 8080
COPY --from=0 /app/build/ympd /usr/bin/ympd
COPY --from=0 /app/build/mkdata /usr/bin/mkdata
CMD ympd

View File

@ -1,59 +1,43 @@
[![Build Status](https://travis-ci.org/notandy/ympd.svg)](https://travis-ci.org/notandy/ympd)
ympd
====
[![Build Status](http://ci.ympd.org/github.com/notandy/ympd/status.svg)](https://ci.ympd.org/github.com/notandy/ympd)
Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
http://www.ympd.org
![ScreenShot](http://www.ympd.org/assets/ympd_github.png)
## Dependencies
Dependencies
------------
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
- cmake 2.6: http://cmake.org/
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
- cmake 2.6: http://cmake.org/
- OpenSSL: https://www.openssl.org/
Unix Build Instructions
-----------------------
## Unix Build Instructions
1. install dependencies. cmake, libmpdclient (dev), and OpenSSL (dev) are available from all major distributions.
2. create build directory `cd /path/to/src; mkdir build; cd build`
3. create makefile `cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr`
4. build `make`
5. install `sudo make install` or just run with `./ympd`
## Run flags
1. install dependencies, cmake and libmpdclient are available from all major distributions.
2. create build directory ```cd /path/to/src; mkdir build; cd build```
3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr```
4. build ```make```
5. install ```sudo make install``` or just run with ```./ympd```
Run flags
---------
```
Usage: ./ympd [OPTION]...
-D, --digest <htdigest> path to htdigest file for authorization
(realm ympd) [no authorization]
-h, --host <host> connect to mpd at host [localhost]
-p, --port <port> connect to mpd at port [6600]
-w, --webport [ip:]<port> listen interface/port for webserver [8080]
-u, --user <username> drop priviliges to user after socket bind
-V, --version get version
--help this help
-h, --host <host> connect to mpd at host [localhost]
-p, --port <port> connect to mpd at port [6600]
-w, --webport [ip:]<port> listen interface/port for webserver [8080]
-u, --user <username> drop priviliges to user after socket bind
-V, --version get version
--help this help
```
## SSL Support
To run ympd with SSL support:
- create a certificate (key and cert in the same file), example:
```
# openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1000 -nodes
# cat key.pem cert.pem > ssl.pem
```
- tell ympd to use a webport using SSL and where to find the certificate:
```
# ./ympd -w "ssl://8081:/path/to/ssl.pem"
```
## Copyright
Copyright
---------
2013-2014 <andy@ndyk.de>

View File

@ -1,8 +1,8 @@
#!/bin/sh
#/bin/sh
### BEGIN INIT INFO
# Provides: ympd
# Required-Start: $remote_fs mpd
# Required-Stop: $remote_fs mpd
# Required-Start: $local_fs $mpd
# Required-Stop: $local_fs $mpd
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Daemonized version of ympd.
@ -20,13 +20,8 @@ PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
LOG_OUT=/var/log/$NAME.out
LOG_ERR=/var/log/$NAME.err
YMPD_USER=nobody
MPD_HOST=localhost
MPD_PORT=6600
WEB_PORT=8080
#DIGEST=--digest /path/to/htdigest
#LOCALPORT=8080
YMPD_USER=mpd
DAEMON_OPT="--user $YMPD_USER --webport 80"
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
@ -37,8 +32,6 @@ WEB_PORT=8080
# Load the VERBOSE setting and other rcS variables
[ -f /etc/default/rcS ] && . /etc/default/rcS
DAEMON_OPT="--user $YMPD_USER --mpdpass '$MPD_PASSWORD' --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST $LOCALPORT"
do_start()
{
start-stop-daemon --start --background --quiet --pidfile $PIDFILE --make-pidfile \

View File

@ -1,6 +0,0 @@
MPD_HOST=localhost
MPD_PORT=6600
MPD_PASSWORD=
WEB_PORT=8080
#DIGEST=--digest /path/to/htdigest
#LOCALPORT=--localport 8080

View File

@ -1,28 +0,0 @@
#!/bin/sh
# PROVIDE: ympd
# REQUIRE: DAEMON musicpd
# KEYWORD: shutdown
# Add the following line to /etc/rc.conf to enable ympd:
#
# ympd_enable="YES"
. /etc/rc.subr
name="ympd"
rcvar="${name}_enable"
command="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
start_cmd="ympd_start"
load_rc_config "${name}"
: ${ympd_enable:="NO"}
ympd_start()
{
check_startmsgs && echo "Starting ${name}."
/usr/sbin/daemon -f -p "${pidfile}" "${command}" "${rc_flags}"
}
run_rc_command "$1"

View File

@ -3,35 +3,11 @@ Description=ympd server daemon
Requires=network.target local-fs.target
[Service]
User=nobody
DynamicUser=yes
MountAPIVFS=yes
RemoveIPC=yes
CapabilityBoundingSet=
LockPersonality=yes
PrivateUsers=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
NoNewPrivileges=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectHome=yes
Environment=MPD_HOST=localhost
Environment=MPD_PORT=6600
Environment=MPD_PASSWORD=
Environment=WEB_PORT=8080
Environment=YMPD_USER=nobody
Environment=DIGEST=
Environment=LOCALPORT=
EnvironmentFile=/etc/default/ympd
ExecStart=/usr/bin/ympd --user $USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST $LOCALPORT
EnvironmentFile=-/etc/conf.d/ympd
ExecStart=/usr/bin/ympd -h $MPD_HOST -p $MPD_PORT -w $WEB_PORT
Type=simple
[Install]

View File

@ -1,71 +0,0 @@
#
# spec file for package ympd
#
# Copyright (c) 2014 Markus S. <kamikazow@web.de>
#
# This file is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
Name: ympd
Version: 1.2.2
Release: 0%{?dist}
Summary: ympd is a lightweight MPD (Music Player Daemon) web client
Group: Applications/Multimedia
License: GPL
URL: http://www.ympd.org/
# For this spec file to work, the ympd sources must be located in a directory
# named ympd-1.2.2 (with "1.2.2" being the version number defined above).
# If the sources are compressed in another format than ZIP, change the
# file extension accordingly.
Source0: %{name}-%{version}.zip
# Package names only verified with Fedora.
# Should the packages in your distro be named dirrerently,
# see http://en.opensuse.org/openSUSE:Build_Service_cross_distribution_howto
# %if 0%{?fedora}
BuildRequires: cmake
BuildRequires: unzip
BuildRequires: libmpdclient-devel
Requires: libmpdclient
# %endif
%description
ympd is a lightweight MPD (Music Player Daemon) web client that runs without
a dedicated webserver or interpreters like PHP, NodeJS or Ruby.
It's tuned for minimal resource usage and requires only very litte dependencies.
%prep
%setup -q
%build
mkdir build
pushd build
%cmake .. -DCMAKE_INSTALL_PREFIX_PATH=%{_prefix}
make PREFIX=%{_prefix} %{?_smp_mflags}
popd
%install
pushd build
%{make_install}
popd
%files
%defattr(-,root,root,-)
%doc LICENSE README.md
%{_bindir}/%{name}
%{_mandir}/man[^3]/*
%changelog

7
htdocs/css/bootstrap-theme.min.css vendored Normal file

File diff suppressed because one or more lines are too long

7
htdocs/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -9,52 +9,23 @@ body {
#volumeslider {
width: 150px;
float: left;
}
#volumeslider .progress {
margin-bottom: 0;
}
button {
overflow: hidden;
}
#volume-icon {
float: left;
margin-right: 10px;
margin-top: 2px;
}
#volume-number {
float: right;
margin-top: 2px;
margin-left: 10px;
}
#love {
float: right;
}
#love > button > span {
color: red;
}
#breadcrump {
display: block;
overflow: auto;
white-space: nowrap;
}
#breadcrump > li > a {
cursor: pointer;
}
#counter {
font-size: 24px;
margin-top: -6px;
margin-left: 10px;
min-width: 50px;
font-size: 24px;
margin-top: -6px;
margin-left: 10px;
min-width: 50px;
}
#search {
@ -62,7 +33,7 @@ button {
}
.btn-group-hover {
opacity: 20%;
opacity: 20%;
}
.btn:active,
@ -70,77 +41,29 @@ button {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
color: #428bca;
background-color: #fdfdfd;
border-color: #adadad;
}
@media (max-width: 1199px) {
#btn-responsive-block > .btn {
padding: 6px 12px;
font-size: 14px;
border-radius: 4px;
#btn-responsive-block > .btn {
padding: 6px 12px;
font-size: 14px;
border-radius: 4px;
}
}
h1 {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
td:nth-child(4),
th:nth-child(4) {
/* This *has* to be placed before
any t[dh]:nth-last-child(2) for
the override to work. */
min-width: 50%;
}
td:nth-last-child(2),
th:nth-last-child(2) {
#salamisandwich td:nth-child(3), th:nth-child(3) {
text-align: right;
width: 4em;
}
#salamisandwich td:nth-child(4) span {
font-size: 90%;
display: block;
}
td:nth-child(2),
td:nth-child(3) {
min-width: 25%;
max-width: 10em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media only screen and (max-width: 600px) {
td:nth-child(2),
td:nth-child(3) {
min-width: 0;
max-width: 0;
}
td:nth-child(4),
th:nth-child(4) {
min-width: 10%;
white-space: normal;
}
}
tbody {
cursor: pointer;
}
td:last-child,
td:first-child {
td:last-child, td:first-child {
width: 30px;
}
@ -149,7 +72,7 @@ td:first-child {
z-index: 9999;
}
/* Positioning */
/* Positioning */
.notifications.top-right {
right: 10px;
top: 60px;
@ -160,30 +83,4 @@ td:first-child {
position: relative;
z-index: 9999;
margin: 5px 0px;
}
button {
overflow: hidden;
}
#trashmode span:last-child {
display: inline-block;
text-align: left;
width: 2.8em;
}
#filter > a.active {
font-weight: bold;
pointer-events: none;
cursor: default;
text-decoration: none;
color: black;
}
@media screen and (min-width: 992px) {
.sticky {
position: sticky;
top: 55px;
z-index: 99;
}
}
}

1
htdocs/css/mpd.min.css vendored Normal file
View File

@ -0,0 +1 @@
body{padding-top:50px;padding-bottom:50px}.starter-template{padding:40px 15px}#volumeslider{width:150px}#volumeslider .progress{margin-bottom:0}#volume-icon{float:left;margin-right:10px;margin-top:2px}#counter{font-size:24px;margin-top:-6px;margin-left:10px;min-width:50px}#search{margin-right:-10px}.btn-group-hover{opacity:20%}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);color:#428bca;background-color:#fdfdfd;border-color:#adadad}@media(max-width:1199px){#btn-responsive-block>.btn{padding:6px 12px;font-size:14px;border-radius:4px}}#salamisandwich td:nth-child(3),th:nth-child(3){text-align:right}tbody{cursor:pointer}td:last-child,td:first-child{width:30px}.notifications{position:fixed;z-index:9999}.notifications.top-right{right:10px;top:60px}.notifications>div{position:relative;z-index:9999;margin:5px 0}

View File

@ -1,597 +1,246 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="ympd - fast and lightweight MPD webclient"
/>
<meta name="author" content="andy@ndyk.de" />
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="ympd - fast and lightweight MPD webclient">
<meta name="author" content="andy@ndyk.de">
<title>ympd</title>
<title>ympd</title>
<!-- Bootstrap core CSS -->
<link href="css/bootstrap.css" rel="stylesheet" />
<link href="css/bootstrap-theme.css" rel="stylesheet" />
<!-- Bootstrap core CSS -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/bootstrap-theme.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="css/mpd.css" rel="stylesheet" />
<link
href="assets/favicon.ico"
rel="shortcut icon"
type="image/vnd.microsoft.icon"
/>
<script src="js/modernizr-custom.js"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button
type="button"
class="navbar-toggle"
data-toggle="collapse"
data-target=".navbar-collapse"
>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/"
><span class="glyphicon glyphicon-play-circle"></span> ympd</a
>
</div>
<div class="collapse navbar-collapse">
<ul id="nav_links" class="nav navbar-nav">
<li id="queue"><a href="#/">Queue</a></li>
<li id="browse"><a href="#/browse/0/">Browse</a></li>
<li>
<a href="#" data-toggle="modal" data-target="#addstream"
>Add Stream</a
>
</li>
<li>
<a
href="#"
data-toggle="modal"
data-target="#settings"
onclick="getHost();"
>Settings</a
>
</li>
</ul>
<!-- Custom styles for this template -->
<link href="css/mpd.min.css" rel="stylesheet">
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
<div class="btn-group">
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_SET_PREV');"
>
<span class="glyphicon glyphicon-backward"></span>
</button>
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_SET_STOP');"
>
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
</button>
<button
type="button"
class="btn btn-default"
onclick="clickPlay();"
>
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
</button>
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_SET_NEXT');"
>
<span class="glyphicon glyphicon-forward"></span>
</button>
</div>
<div class="btn-group" role="group">
<button
type="button"
class="btn btn-default"
onclick="clickLocalPlay()"
>
<audio id="player" preload="none"></audio>
<span
id="localplay-icon"
class="glyphicon glyphicon-play"
></span>
</button>
</div>
<div class="btn-group" role="group" id="volume-group">
<div class="btn btn-toolbar btn-default">
<span
id="volume-icon"
class="glyphicon glyphicon-volume-up"
></span>
<div id="volumeslider"></div>
<span id="volume-number"></span>
</div>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/"><span class="glyphicon glyphicon-play-circle"></span> ympd</a>
</div>
<div class="collapse navbar-collapse">
<ul id="nav_links" class="nav navbar-nav">
<li id="queue"><a href="#/">Queue</a></li>
<li id="browse"><a href="#/browse/0/">Browse database</a></li>
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
</ul>
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
<div class="btn-group">
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_PREV');">
<span class="glyphicon glyphicon-backward"></span>
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_STOP');">
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
</button>
<button type="button" class="btn btn-default" onclick="clickPlay();">
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_NEXT');">
<span class="glyphicon glyphicon-forward"></span>
</button>
</div>
<div class="btn-group">
<div class="btn btn-toolbar btn-default">
<span id="volume-icon" class="glyphicon glyphicon-volume-up"></span>
<div id="volumeslider"></div>
</div>
</div>
</div>
<form id="search" class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
</form>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container starter-template">
<div class="row">
<div class="col-md-10 col-xs-12">
<div class="notifications top-right"></div>
<div class="panel panel-primary">
<!-- Default panel contents -->
<div class="panel-heading"><b id="panel-heading">Queue</b></div>
<div class="panel-body">
<h1>
<span id="track-icon" class="glyphicon glyphicon-play"></span>
<span id="currenttrack"></span>
</h1>
<h4>
<span id="album" class="text"></span>
<span id="artist" class="text pull-right"></span>
</h4>
<p id="counter" class="text pull-right">&nbsp;&nbsp;</p>
<div id="progressbar"></div>
</div><!-- /.panel-body -->
<ol id="breadcrump" class="breadcrumb">
</ol>
<!-- Table -->
<table id="salamisandwich" class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Duration</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div><!-- /.panel -->
<ul class="pager">
<li id="prev" class="page-btn hide"><a href="">Previous</a></li>
<li id="next" class="page-btn"><a href="">Next</a></li>
</ul>
</div><!-- /.col-md-10 -->
<div class="col-md-2 col-xs-12" >
<div class="btn-toolbar">
<div class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
<button id="btnrandom" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-random"></span> Random
</button>
<button id="btnconsume" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-fire"></span> Consume
</button>
<button id="btnsingle" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-star"></span> Single
</button>
<button id="btnrepeat" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-repeat"></span> Repeat
</button>
</div>
<div id="btn-responsive-block" class="btn-group-vertical btn-block btn-group-lg">
<button type="button" class="btn btn-default" onclick="updateDB();">
<span class="glyphicon glyphicon-refresh"></span> Update DB
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_RM_ALL');">
<span class="glyphicon glyphicon-trash"></span> Clear queue
</button>
</div>
<div id="btn-responsive-block" class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
<button type="button" class="btn btn-default" id="btnnotify">
<span class="glyphicon glyphicon-comment"></span> Notifications
</button>
</div>
</div>
</div><!-- /.col-md-2 -->
</div><!-- /.row -->
</div><!-- /.container -->
<!-- Modal -->
<div class="modal fade" id="settings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h2 class="modal-title" id="settingsLabel"><span class="glyphicon glyphicon-wrench"></span> Settings</h2>
</div>
<div class="modal-body">
<h4><a href="http://www.ympd.org"><span class="glyphicon glyphicon-play-circle"></span> ympd</a>&nbsp;&nbsp;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
<p>
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated webserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.</p>
<h5>ympd uses following excellent software:</h5>
<h6><a href="http://cesanta.com/docs.html">Mongoose</a> <small>GPLv2</small></h6>
<h6><a href="http://www.musicpd.org/libs/libmpdclient/">libMPDClient</a> <small>BSD License</small></h6>
<hr />
<form role="form">
<div class="row">
<div class="form-group col-md-9">
<label class="control-label" for="mpdhost">MPD Host/IP</label>
<input type="text" class="form-control" id="mpdhost" />
</div>
<div class="form-group col-md-3">
<label class="control-label" for="mpdport">MPD Port</label>
<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 class="form-group col-md-12">
<div id="mpd_password_set" class="hide alert alert-info">
<button type="button" class="close" aria-hidden="true">&times;</button>
MPD Password is set
</div>
</div>
<form id="search" class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" />
</div>
</form>
</div>
<!--/.nav-collapse -->
</div>
</div>
<div class="container starter-template">
<div class="row">
<div class="col-md-10 col-xs-12">
<div class="notifications top-right"></div>
<div class="panel panel-primary sticky">
<!-- Default panel contents -->
<div class="panel-body">
<h1>
<span
id="track-icon"
onclick="clickPlay();"
class="glyphicon glyphicon-play"
></span>
<span id="currenttrack"></span>
<span id="love">
<button
id="btnlove"
type="button"
class="btn btn-default"
onclick="clickLove();"
>
<span class="glyphicon glyphicon-heart"></span></button
></span>
</h1>
<h4>
<span id="artist" class="text"></span>
<span id="album" class="text pull-right"></span>
</h4>
<p id="counter" class="text pull-right">&nbsp;&nbsp;</p>
<div id="progressbar"></div>
</div>
</div>
<!-- /.panel -->
<!-- /.panel-body -->
<div class="panel panel-primary">
<div class="panel-heading">
<b id="panel-heading">Queue</b>
<b id="panel-heading-info" class="text pull-right"></b>
</div>
<ol id="breadcrump" class="breadcrumb"></ol>
<div class="col-md-12" id="filter"></div>
<!-- Table -->
<table id="salamisandwich" class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Artist</th>
<th>Album</th>
<th>Title</th>
<th>Length</th>
<th><div style="width: 15px"></div></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<!-- /.panel -->
<ul class="pager">
<li id="prev" class="page-btn hide"><a href="">Previous</a></li>
<li id="next" class="page-btn"><a href="">Next</a></li>
</ul>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-default" onclick="confirmSettings();">Save</button>
</div>
<!-- /.col-md-10 -->
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="col-md-2 col-xs-12 sticky">
<div class="btn-toolbar">
<div
class="btn-group-vertical btn-block btn-group-lg"
data-toggle="buttons"
>
<button id="btnrandom" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-random"></span> Random
</button>
<button id="btnconsume" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-fire"></span> Consume
</button>
<button id="btnsingle" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-star"></span> Single
</button>
<button id="btncrossfade" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-link"></span> Crossfade
</button>
<button id="btnrepeat" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-repeat"></span> Repeat
</button>
</div>
<div
id="btn-outputs-block"
class="btn-group-vertical btn-block btn-group-lg"
></div>
<div
id="trashmode"
class="btn-group-vertical btn-block btn-group-lg"
data-toggle="radio"
>
<button id="btntrashmodeup" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-up"></span>
<span class="glyphicon glyphicon-trash"></span> <span>Up</span>
</button>
<button
id="btntrashmodesingle"
type="button"
class="btn btn-default active"
>
<span class="glyphicon glyphicon-star-empty"></span>
<span class="glyphicon glyphicon-trash"></span>
<span>Single</span>
</button>
<button
id="btntrashmodedown"
type="button"
class="btn btn-default"
>
<span class="glyphicon glyphicon-chevron-down"></span>
<span class="glyphicon glyphicon-trash"></span>
<span>Down</span>
</button>
</div>
<div
id="btn-responsive-block"
class="btn-group-vertical btn-block btn-group-lg"
>
<button
type="button"
class="btn btn-default"
onclick="socket.send('MPD_API_RM_ALL');"
>
<span class="glyphicon glyphicon-trash"></span> Clear Queue
</button>
<a
href="#"
data-toggle="modal"
data-target="#savequeue"
class="btn btn-default"
>
<span class="glyphicon glyphicon-save"></span> Save Queue
</a>
</div>
</div>
<div class="modal fade bs-example-modal-sm" id="wait" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1>Searching...</h1>
</div>
<!-- /.col-md-2 -->
</div>
<!-- /.row -->
</div>
<!-- /.container -->
<!-- Modal -->
<div
class="modal fade"
id="settings"
tabindex="-1"
role="dialog"
aria-labelledby="settingsLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-hidden="true"
>
&times;
</button>
<h2 class="modal-title" id="settingsLabel">
<span class="glyphicon glyphicon-wrench"></span> Settings
</h2>
</div>
<div class="modal-body">
<h4>
<a href="http://www.ympd.org"
><span class="glyphicon glyphicon-play-circle"></span> ympd</a
>&nbsp;&nbsp;&nbsp;<small
>MPD Web GUI - written in C, utilizing Websockets and
Bootstrap/JS</small
>
</h4>
<p>
ympd is a lightweight MPD (Music Player Daemon) web client that
runs without a dedicated webserver or interpreters like PHP,
NodeJS or Ruby. It's tuned for minimal resource usage and requires
only very litte dependencies.
</p>
<h5>ympd uses following excellent software:</h5>
<h6>
<a href="http://cesanta.com/docs.html">Mongoose</a>
<small>GPLv2</small>
</h6>
<h6>
<a href="http://www.musicpd.org/libs/libmpdclient/"
>libMPDClient</a
>
<small>BSD License</small>
</h6>
<hr />
<div class="row">
<div class="form-group col-md-6">
<button
type="button"
class="btn btn-default btn-block"
onclick="updateDB();"
>
<span class="glyphicon glyphicon-refresh"></span> Update
Database
</button>
</div>
<div class="form-group col-md-6" data-toggle="buttons">
<button
type="button"
class="btn btn-default btn-block"
id="btnnotify"
>
<span class="glyphicon glyphicon-comment"></span> Enable
Notifications
</button>
</div>
</div>
<hr />
<form role="form">
<div class="row">
<div class="form-group col-md-9">
<label class="control-label" for="mpdhost">MPD Host/IP</label>
<input type="text" class="form-control" id="mpdhost" />
</div>
<div class="form-group col-md-3">
<label class="control-label" for="mpdport">MPD Port</label>
<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="Confirmation"
data-placement="right"
data-toggle="popover"
data-content="Password does not match!"
data-trigger="manual"
/>
</div>
<div class="form-group col-md-12">
<div id="mpd_password_set" class="hide alert alert-info">
<button type="button" class="close" aria-hidden="true">
&times;
</button>
MPD Password is set
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-12">
<label class="control-label" for="mpdstream"
>MPD Stream URL</label
>
<input type="text" class="form-control" id="mpdstream" />
</div>
</div>
</form>
<div class="row">
<div class="form-group col-md-12" data-toggle="buttons">
<button
type="button"
class="btn btn-default btn-block"
id="btnautoplay"
>
<span class="glyphicon glyphicon-play"></span> Autoplay stream
in this browser when mpd is playing
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
<button
type="button"
class="btn btn-default"
onclick="confirmSettings();"
>
Save
</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<!-- Modal -->
<div
class="modal fade"
id="addstream"
tabindex="-1"
role="dialog"
aria-labelledby="addstreamLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-hidden="true"
>
&times;
</button>
<h2 class="modal-title" id="addstreamLabel">
<span class="glyphicon glyphicon-wrench"></span> Add Stream
</h2>
</div>
<div class="modal-body">
<form role="form">
<div class="row">
<div class="form-group col-md-12">
<label class="control-label" for="streamurl"
>Stream URL</label
>
<input type="text" class="form-control" id="streamurl" />
</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"
onclick="addStream();"
>
Add Stream
</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div
class="modal fade"
id="savequeue"
tabindex="-1"
role="dialog"
aria-labelledby="savequeueLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-hidden="true"
>
&times;
</button>
<h2 class="modal-title" id="savequeueLabel">
<span class="glyphicon glyphicon-wrench"></span> Save Queue
</h2>
</div>
<div class="modal-body">
<form role="form">
<div class="row">
<div class="form-group col-md-9">
<label class="control-label" for="playlistname"
>Playlist Name</label
>
<input type="text" class="form-control" id="playlistname" />
</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"
onclick="saveQueue();"
>
Save Queue
</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div
class="modal fade bs-example-modal-sm"
id="wait"
tabindex="-1"
role="dialog"
data-backdrop="static"
data-keyboard="false"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1>Searching...</h1>
</div>
<div class="modal-body">
<div class="progress progress-striped active">
<div
class="progress-bar"
role="progressbar"
aria-valuenow="45"
aria-valuemin="0"
aria-valuemax="100"
style="width: 100%"
>
<span class="sr-only">Please Wait</span>
</div>
<div class="modal-body">
<div class="progress progress-striped active">
<div class="progress-bar" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
<span class="sr-only">Please Wait</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/jquery.cookie.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/bootstrap-notify.js"></script>
<script src="js/bootstrap-slider.js"></script>
<script src="js/sammy.js"></script>
<script src="js/jquery-ui-sortable.min.js"></script>
<script src="js/mpd.js"></script>
</body>
<!-- Placed at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/jquery.cookie.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/bootstrap-notify.js"></script>
<script src="js/bootstrap-slider.js"></script>
<script src="js/sammy.js"></script>
<script src="js/mpd.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <error.h>
#include <errno.h>
#include <string.h>
@ -59,17 +59,17 @@ int main(int argc, char *argv[])
FILE *fd;
if(argc <= 1)
err(EXIT_FAILURE, "Usage: ./%s <this_file> <file1> [file2, ...] > embedded_data.c", argv[0]);
error(EXIT_FAILURE, 0, "Usage: ./%s <this_file> <file1> [file2, ...] > embedded_data.c", argv[0]);
for(i = 1; i < argc; i++)
{
fd = fopen(argv[i], "r");
if(!fd)
err(EXIT_FAILURE, "%s", argv[i]);
printf("static const unsigned char v%d[] = {", i);
j = 0;
fd = fopen(argv[i], "r");
if(!fd)
error(EXIT_FAILURE, errno, "Failed open file %s", argv[i]);
j = 0;
while((buf = fgetc(fd)) != EOF)
{
if(!(j % 12))

View File

@ -22,8 +22,8 @@
#define YMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}
#define YMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}
#define YMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
#define SRC_PATH "${ASSETS_PATH}"
#define DATADIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}"
#define LOCAL_RESOURCE_PATH DATADIR"/htdocs"
#cmakedefine WITH_MPD_HOST_CHANGE
#cmakedefine WITH_DYNAMIC_ASSETS
#endif

View File

@ -1,7 +1,7 @@
/* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
@ -16,45 +16,28 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "http_server.h"
#include <openssl/rand.h>
#include <string.h>
#include "mpd_client.h"
#include "http_server.h"
int callback_http(struct mg_connection *c) {
int callback_http(struct mg_connection *c)
{
const struct embedded_file *req_file;
if (!strcmp(c->uri, "/"))
if(!strcmp(c->uri, "/"))
req_file = find_embedded_file("/index.html");
else
req_file = find_embedded_file(c->uri);
if (req_file) {
if(req_file)
{
mg_send_header(c, "Content-Type", req_file->mimetype);
mg_send_data(c, req_file->data, req_file->size);
return MG_TRUE;
}
if (!strcmp(c->uri, "/wss-auth")) {
unsigned char salt[WSS_AUTH_TOKEN_SIZE + 1];
RAND_bytes(salt, WSS_AUTH_TOKEN_SIZE);
for (int i = 0; i <= WSS_AUTH_TOKEN_SIZE; i++) salt[i] = salt[i] % 26 + 65;
salt[WSS_AUTH_TOKEN_SIZE] = 0;
if (mpd.wss_auth_token)
free(mpd.wss_auth_token);
mpd.wss_auth_token = strdup((char *)salt);
mg_send_header(c, "Content-Type", "text/plain");
mg_send_data(c, salt, WSS_AUTH_TOKEN_SIZE);
return MG_TRUE;
return MG_REQUEST_PROCESSED;
}
mg_send_status(c, 404);
mg_printf_data(c, "Not Found");
return MG_TRUE;
return MG_REQUEST_PROCESSED;
}

View File

@ -1,7 +1,7 @@
/* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
@ -22,13 +22,14 @@
#include "mongoose.h"
struct embedded_file {
const char *name;
const unsigned char *data;
const char *mimetype;
size_t size;
const char *name;
const unsigned char *data;
const char *mimetype;
size_t size;
};
const struct embedded_file *find_embedded_file(const char *name);
int callback_http(struct mg_connection *c);
#endif

View File

@ -1,7 +1,7 @@
/* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
@ -15,7 +15,7 @@
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __JSON_ENCODE_H__
#define __JSON_ENCODE_H__

File diff suppressed because it is too large Load Diff

View File

@ -2,27 +2,28 @@
// Copyright (c) 2013-2014 Cesanta Software Limited
// All rights reserved
//
// This software is dual-licensed: you can redistribute it and/or modify
// 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 software under the terms of the GNU General
// 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 software under a commercial
// 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.6"
#define MONGOOSE_VERSION "5.3"
#include <stdio.h> // required for FILE
#include <stddef.h> // required for size_t
#include <sys/types.h> // required for time_t
#ifdef __cplusplus
extern "C" {
@ -36,7 +37,7 @@ struct mg_connection {
const char *query_string; // URL part after '?', not including '?', or NULL
char remote_ip[48]; // Max IPv6 string length is 45 characters
char local_ip[48]; // Local IP address
const char *local_ip; // Local IP address
unsigned short remote_port; // Client's port
unsigned short local_port; // Local port number
@ -47,72 +48,46 @@ struct mg_connection {
} http_headers[30];
char *content; // POST (or websocket message) data, or NULL
size_t content_len; // Data length
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_create_server()
void *server_param; // Parameter passed to mg_add_uri_handler()
void *connection_param; // Placeholder for connection-specific data
void *callback_param;
void *callback_param; // Used by mg_iterate_over_connections()
};
struct mg_server; // Opaque structure describing server instance
enum mg_result { MG_FALSE, MG_TRUE, MG_MORE };
enum mg_event {
MG_POLL = 100, // Callback return value is ignored
MG_CONNECT, // If callback returns MG_FALSE, connect fails
MG_AUTH, // If callback returns MG_FALSE, authentication fails
MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req
MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection
MG_RECV, // Mongoose has received POST data chunk.
// Callback should return a number of bytes to discard from
// the receive buffer, or -1 to close the connection.
MG_CLOSE, // Connection is closed, callback return value is ignored
MG_WS_HANDSHAKE, // New websocket connection, handshake request
MG_WS_CONNECT, // New websocket connection established
MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err
};
typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event);
// Websocket opcodes, from http://tools.ietf.org/html/rfc6455
enum {
WEBSOCKET_OPCODE_CONTINUATION = 0x0,
WEBSOCKET_OPCODE_TEXT = 0x1,
WEBSOCKET_OPCODE_BINARY = 0x2,
WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8,
WEBSOCKET_OPCODE_PING = 0x9,
WEBSOCKET_OPCODE_PONG = 0xa
};
typedef int (*mg_handler_t)(struct mg_connection *);
// Server management functions
struct mg_server *mg_create_server(void *server_param, mg_handler_t handler);
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);
time_t mg_poll_server(struct mg_server *, int milliseconds);
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_copy_listeners(struct mg_server *from, struct mg_server *to);
struct mg_connection *mg_next(struct mg_server *, struct mg_connection *);
void mg_wakeup_server(struct mg_server *);
void mg_wakeup_server_ex(struct mg_server *, mg_handler_t, const char *, ...);
struct mg_connection *mg_connect(struct mg_server *, const char *);
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);
size_t mg_send_data(struct mg_connection *, const void *data, int data_len);
size_t mg_printf_data(struct mg_connection *, const char *format, ...);
size_t mg_write(struct mg_connection *, const void *buf, size_t len);
size_t mg_printf(struct mg_connection *conn, const char *fmt, ...);
void mg_send_data(struct mg_connection *, const void *data, int data_len);
void mg_printf_data(struct mg_connection *, const char *format, ...);
size_t mg_websocket_write(struct mg_connection *, int opcode,
const char *data, size_t data_len);
size_t mg_websocket_printf(struct mg_connection* conn, int opcode,
const char *fmt, ...);
int mg_websocket_write(struct mg_connection *, int opcode,
const char *data, size_t data_len);
void mg_send_file(struct mg_connection *, const char *path, const char *);
void mg_send_file_data(struct mg_connection *, int fd);
// 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);
@ -124,26 +99,24 @@ int mg_parse_multipart(const char *buf, int buf_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);
size_t mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len);
int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, int);
int mg_terminate_ssl(struct mg_connection *c, const char *cert);
int mg_forward(struct mg_connection *c, const char *addr);
void *mg_mmap(FILE *fp, size_t size);
void mg_munmap(void *p, size_t size);
// 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 };
// Templates support
struct mg_expansion {
const char *keyword;
void (*handler)(struct mg_connection *);
// HTTP client events
enum {
MG_CONNECT_SUCCESS, MG_CONNECT_FAILURE,
MG_DOWNLOAD_SUCCESS, MG_DOWNLOAD_FAILURE
};
void mg_template(struct mg_connection *, const char *text,
struct mg_expansion *expansions);
int mg_connect(struct mg_server *, const char *host, int port, int use_ssl,
mg_handler_t handler, void *param);
#ifdef __cplusplus
}

View File

@ -1,7 +1,7 @@
/* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
@ -16,87 +16,48 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "mpd_client.h"
#include <libgen.h>
#include <mpd/client.h>
#include <mpd/message.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <mpd/client.h>
#include "mpd_client.h"
#include "config.h"
#include "json_encode.h"
/* forward declaration */
static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev);
struct t_mpd mpd;
const char * mpd_cmd_strs[] = {
MPD_CMDS(GEN_STR)
};
const char *mpd_cmd_strs[] = {MPD_CMDS(GEN_STR)};
char *get_arg1(char *p) {
return strchr(p, ',') + 1;
}
char *get_arg2(char *p) {
return get_arg1(get_arg1(p));
}
static inline enum mpd_cmd_ids get_cmd_id(char *cmd) {
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])))
static inline enum mpd_cmd_ids get_cmd_id(char *cmd)
{
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;
return -1;
}
int callback_mpd(struct mg_connection *c) {
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 = NULL, *token;
char *p_charbuf = NULL;
if (!c->connection_param)
c->connection_param = calloc(1, sizeof(struct t_mpd_client_session));
if(cmd_id == -1)
return MG_CLIENT_CONTINUE;
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
if (!s->authorized && (cmd_id != MPD_API_AUTHORIZE)) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"not authorized\"}");
mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE;
}
if (cmd_id == -1)
return MG_TRUE;
if (mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST &&
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_TRUE;
return MG_CLIENT_CONTINUE;
switch (cmd_id) {
case MPD_API_AUTHORIZE:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_AUTHORIZE"))
goto out_authorize;
if ((token = strtok(NULL, ",")) == NULL)
goto out_authorize;
free(p_charbuf);
p_charbuf = strdup(c->content);
s->auth_token = strdup(get_arg1(p_charbuf));
if (!strcmp(mpd.wss_auth_token, s->auth_token))
s->authorized = 1;
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"authorized\", \"data\":\"%s\"}",
s->authorized ? "true" : "false");
out_authorize:
free(p_charbuf);
break;
mpd_connection_set_timeout(mpd.conn, 10000);
switch(cmd_id)
{
case MPD_API_UPDATE_DB:
mpd_run_update(mpd.conn, NULL);
break;
@ -119,306 +80,184 @@ int callback_mpd(struct mg_connection *c) {
mpd_run_clear(mpd.conn);
break;
case MPD_API_RM_TRACK:
if (sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
mpd_run_delete_id(mpd.conn, uint_buf);
break;
case MPD_API_RM_RANGE:
if (sscanf(c->content, "MPD_API_RM_RANGE,%u,%u", &uint_buf, &uint_buf_2))
mpd_run_delete_range(mpd.conn, uint_buf, uint_buf_2);
break;
case MPD_API_MOVE_TRACK:
if (sscanf(c->content, "MPD_API_MOVE_TRACK,%u,%u", &uint_buf, &uint_buf_2) == 2) {
uint_buf -= 1;
uint_buf_2 -= 1;
mpd_run_move(mpd.conn, uint_buf, uint_buf_2);
}
break;
case MPD_API_PLAY_TRACK:
if (sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
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))
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))
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))
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))
if(sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf))
mpd_run_single(mpd.conn, uint_buf);
break;
case MPD_API_TOGGLE_CROSSFADE:
if (sscanf(c->content, "MPD_API_TOGGLE_CROSSFADE,%u", &uint_buf))
mpd_run_crossfade(mpd.conn, uint_buf);
break;
case MPD_API_GET_OUTPUTS:
mpd.buf_size = mpd_put_outputs(mpd.buf, 1);
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
break;
case MPD_API_TOGGLE_OUTPUT:
if (sscanf(c->content, "MPD_API_TOGGLE_OUTPUT,%u,%u", &uint_buf, &uint_buf_2)) {
if (uint_buf_2)
mpd_run_enable_output(mpd.conn, uint_buf);
else
mpd_run_disable_output(mpd.conn, uint_buf);
}
break;
case MPD_API_SET_VOLUME:
if (sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100)
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))
if(sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2))
mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2);
break;
case MPD_API_GET_QUEUE:
if (sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
if(sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
n = mpd_put_queue(mpd.buf, uint_buf);
break;
case MPD_API_GET_BROWSE:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_GET_BROWSE"))
goto out_browse;
uint_buf = strtoul(strtok(NULL, ","), NULL, 10);
if ((token = strtok(NULL, ",")) == NULL)
goto out_browse;
free(p_charbuf);
p_charbuf = strdup(c->content);
n = mpd_put_browse(mpd.buf, get_arg2(p_charbuf), uint_buf);
out_browse:
free(p_charbuf);
if(sscanf(c->content, "MPD_API_GET_BROWSE,%u,%m[^\t\n]", &uint_buf, &p_charbuf) && p_charbuf != NULL)
{
n = mpd_put_browse(mpd.buf, p_charbuf, uint_buf);
free(p_charbuf);
}
break;
case MPD_API_ADD_TRACK:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_TRACK"))
goto out_add_track;
if ((token = strtok(NULL, ",")) == NULL)
goto out_add_track;
free(p_charbuf);
p_charbuf = strdup(c->content);
mpd_run_add(mpd.conn, get_arg1(p_charbuf));
out_add_track:
free(p_charbuf);
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);
}
break;
case MPD_API_ADD_PLAY_TRACK:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAY_TRACK"))
goto out_play_track;
if ((token = strtok(NULL, ",")) == NULL)
goto out_play_track;
free(p_charbuf);
p_charbuf = strdup(c->content);
int_buf = mpd_run_add_id(mpd.conn, get_arg1(p_charbuf));
if (int_buf != -1)
mpd_run_play_id(mpd.conn, int_buf);
out_play_track:
free(p_charbuf);
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);
}
break;
case MPD_API_ADD_PLAYLIST:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAYLIST"))
goto out_playlist;
if ((token = strtok(NULL, ",")) == NULL)
goto out_playlist;
free(p_charbuf);
p_charbuf = strdup(c->content);
mpd_run_load(mpd.conn, get_arg1(p_charbuf));
out_playlist:
free(p_charbuf);
break;
case MPD_API_SAVE_QUEUE:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SAVE_QUEUE"))
goto out_save_queue;
if ((token = strtok(NULL, ",")) == NULL)
goto out_save_queue;
free(p_charbuf);
p_charbuf = strdup(c->content);
mpd_run_save(mpd.conn, get_arg1(p_charbuf));
out_save_queue:
free(p_charbuf);
if(sscanf(c->content, "MPD_API_ADD_PLAYLIST,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
{
mpd_run_load(mpd.conn, p_charbuf);
free(p_charbuf);
}
break;
case MPD_API_SEARCH:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEARCH"))
goto out_search;
if ((token = strtok(NULL, ",")) == NULL)
goto out_search;
free(p_charbuf);
p_charbuf = strdup(c->content);
n = mpd_search(mpd.buf, get_arg1(p_charbuf));
out_search:
free(p_charbuf);
break;
case MPD_API_SEND_MESSAGE:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEND_MESSAGE"))
goto out_send_message;
if ((token = strtok(NULL, ",")) == NULL)
goto out_send_message;
free(p_charbuf);
p_charbuf = strdup(get_arg1(c->content));
if (strtok(p_charbuf, ",") == NULL)
goto out_send_message;
if ((token = strtok(NULL, ",")) == NULL)
goto out_send_message;
mpd_run_send_message(mpd.conn, p_charbuf, token);
out_send_message:
free(p_charbuf);
break;
case MPD_API_GET_CHANNELS:
mpd.buf_size = mpd_put_channels(mpd.buf);
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
if(sscanf(c->content, "MPD_API_SEARCH,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
{
n = mpd_search(mpd.buf, p_charbuf);
free(p_charbuf);
}
break;
#ifdef WITH_MPD_HOST_CHANGE
/* Commands allowed when disconnected from MPD server */
case MPD_API_SET_MPDHOST:
int_buf = 0;
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDHOST"))
goto out_host_change;
if ((int_buf = strtol(strtok(NULL, ","), NULL, 10)) <= 0)
goto out_host_change;
if ((token = strtok(NULL, ",")) == NULL)
goto out_host_change;
strncpy(mpd.host, token, sizeof(mpd.host));
mpd.port = int_buf;
mpd.conn_state = MPD_RECONNECT;
free(p_charbuf);
return MG_TRUE;
out_host_change:
free(p_charbuf);
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;
}
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");
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": "
"{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}"
"}", mpd.host, mpd.port, mpd.password ? "true" : "false");
break;
case MPD_API_SET_MPDPASS:
p_charbuf = strdup(c->content);
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDPASS"))
goto out_set_pass;
if(sscanf(c->content, "MPD_API_SET_MPDPASS,%m[^\t\n ]", &p_charbuf))
{
if(mpd.password)
free(mpd.password);
if ((token = strtok(NULL, ",")) == NULL)
goto out_set_pass;
if (mpd.password)
free(mpd.password);
mpd.password = strdup(token);
mpd.conn_state = MPD_RECONNECT;
free(p_charbuf);
return MG_TRUE;
out_set_pass:
free(p_charbuf);
mpd.password = p_charbuf;
mpd.conn_state = MPD_RECONNECT;
return MG_CLIENT_CONTINUE;
}
break;
#endif
}
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));
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)
if(n > 0)
mg_websocket_write(c, 1, mpd.buf, n);
return MG_TRUE;
return MG_CLIENT_CONTINUE;
}
int mpd_close_handler(struct mg_connection *c) {
int mpd_close_handler(struct mg_connection *c)
{
/* Cleanup session data */
if (c->connection_param) {
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
if (s->auth_token)
free(s->auth_token);
if(c->connection_param)
free(c->connection_param);
}
return 0;
}
static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) {
static int mpd_notify_callback(struct mg_connection *c) {
size_t n;
if (!c->is_websocket)
return MG_TRUE;
if(!c->is_websocket)
return MG_REQUEST_PROCESSED;
if (!c->connection_param)
return MG_TRUE;
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 (!s->authorized)
return MG_TRUE;
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_TRUE;
}
if (mpd.conn_state != MPD_CONNECTED) {
if(mpd.conn_state != MPD_CONNECTED) {
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}");
mg_websocket_write(c, 1, mpd.buf, n);
} else {
}
else
{
mg_websocket_write(c, 1, mpd.buf, mpd.buf_size);
if (s->song_id != mpd.song_id) {
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) {
if(s->queue_version != mpd.queue_version)
{
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_queue\"}");
mg_websocket_write(c, 1, mpd.buf, n);
s->queue_version = mpd.queue_version;
}
}
return MG_TRUE;
return MG_REQUEST_PROCESSED;
}
void mpd_poll(struct mg_server *s) {
void mpd_poll(struct mg_server *s)
{
switch (mpd.conn_state) {
case MPD_DISCONNECTED:
/* Try to connect */
@ -432,33 +271,23 @@ void mpd_poll(struct mg_server *s) {
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn);
mpd_notify_callback(c, MG_POLL);
}
mg_iterate_over_connections(s, mpd_notify_callback,
(void *)mpd_connection_get_error_message(mpd.conn));
mpd.conn_state = MPD_FAILURE;
return;
}
if (mpd.password && !mpd_run_password(mpd.conn, mpd.password)) {
if(mpd.password && !mpd_run_password(mpd.conn, mpd.password))
{
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn);
mpd_notify_callback(c, MG_POLL);
}
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_connection_set_timeout(mpd.conn, 10000);
mpd.conn_state = MPD_CONNECTED;
/* write outputs */
mpd.buf_size = mpd_put_outputs(mpd.buf, 1);
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
}
break;
case MPD_FAILURE:
@ -466,7 +295,7 @@ void mpd_poll(struct mg_server *s) {
case MPD_DISCONNECT:
case MPD_RECONNECT:
if (mpd.conn != NULL)
if(mpd.conn != NULL)
mpd_connection_free(mpd.conn);
mpd.conn = NULL;
mpd.conn_state = MPD_DISCONNECTED;
@ -474,69 +303,25 @@ void mpd_poll(struct mg_server *s) {
case MPD_CONNECTED:
mpd.buf_size = mpd_put_state(mpd.buf, &mpd.song_id, &mpd.queue_version);
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
}
mpd.buf_size = mpd_put_outputs(mpd.buf, 0);
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
}
mpd.buf_size = mpd_put_channels(mpd.buf);
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
c->callback_param = NULL;
mpd_notify_callback(c, MG_POLL);
}
mg_iterate_over_connections(s, mpd_notify_callback, NULL);
break;
}
}
char *mpd_get_title(struct mpd_song const *song) {
char* mpd_get_title(struct mpd_song const *song)
{
char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
if (str == NULL) {
if(str == NULL){
str = basename((char *)mpd_song_get_uri(song));
}
return str;
}
char *mpd_get_album(struct mpd_song const *song) {
char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
if (str == NULL) {
str = "-";
}
return str;
}
char *mpd_get_artist(struct mpd_song const *song) {
char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
if (str == NULL) {
str = "-";
}
return str;
}
char *mpd_get_year(struct mpd_song const *song) {
char *str;
str = (char *)mpd_song_get_tag(song, MPD_TAG_DATE, 0);
if (str == NULL) {
str = "-";
}
return str;
}
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version) {
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
{
struct mpd_status *status;
int len;
@ -548,18 +333,22 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version) {
}
len = snprintf(buffer, MAX_SIZE,
"{\"type\":\"state\", \"data\":{"
" \"state\":%d, \"volume\":%d, \"repeat\":%d,"
" \"single\":%d, \"crossfade\":%d, \"consume\":%d, \"random\":%d, "
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
" \"currentsongid\": %d"
"}}",
mpd_status_get_state(status), mpd_status_get_volume(status),
mpd_status_get_repeat(status), mpd_status_get_single(status),
mpd_status_get_crossfade(status), mpd_status_get_consume(status),
mpd_status_get_random(status), mpd_status_get_song_pos(status),
mpd_status_get_elapsed_time(status), mpd_status_get_total_time(status),
mpd_status_get_song_id(status));
"{\"type\":\"state\", \"data\":{"
" \"state\":%d, \"volume\":%d, \"repeat\":%d,"
" \"single\":%d, \"consume\":%d, \"random\":%d, "
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
" \"currentsongid\": %d"
"}}",
mpd_status_get_state(status),
mpd_status_get_volume(status),
mpd_status_get_repeat(status),
mpd_status_get_single(status),
mpd_status_get_consume(status),
mpd_status_get_random(status),
mpd_status_get_song_pos(status),
mpd_status_get_elapsed_time(status),
mpd_status_get_total_time(status),
mpd_status_get_song_id(status));
*current_song_id = mpd_status_get_song_id(status);
*queue_version = mpd_status_get_queue_version(status);
@ -567,81 +356,32 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version) {
return len;
}
int mpd_put_outputs(char *buffer, int names) {
struct mpd_output *out;
int nout;
char *str, *strend;
str = buffer;
strend = buffer + MAX_SIZE;
str += snprintf(str, strend - str, "{\"type\":\"%s\", \"data\":{",
names ? "outputnames" : "outputs");
mpd_send_outputs(mpd.conn);
nout = 0;
while ((out = mpd_recv_output(mpd.conn)) != NULL) {
if (nout++)
*str++ = ',';
if (names)
str += snprintf(str, strend - str, " \"%d\":\"%s\"", mpd_output_get_id(out),
mpd_output_get_name(out));
else
str += snprintf(str, strend - str, " \"%d\":%d", mpd_output_get_id(out),
mpd_output_get_enabled(out));
mpd_output_free(out);
}
if (!mpd_response_finish(mpd.conn)) {
fprintf(stderr, "MPD outputs: %s\n", mpd_connection_get_error_message(mpd.conn));
mpd_connection_clear_error(mpd.conn);
return 0;
}
str += snprintf(str, strend - str, " }}");
return str - buffer;
}
int mpd_put_channels(char *buffer) {
struct mpd_pair *channel;
int nchan;
char *str, *strend;
str = buffer;
strend = buffer + MAX_SIZE;
str += snprintf(str, strend - str, "{\"type\":\"%s\", \"data\":{", "channels");
mpd_send_channels(mpd.conn);
nchan = 0;
while ((channel = mpd_recv_channel_pair(mpd.conn)) != NULL) {
if (nchan++)
*str++ = ',';
str += snprintf(str, strend - str, " \"%d\":\"%s\"", nchan, channel->value);
mpd_return_pair(mpd.conn, channel);
}
if (!mpd_response_finish(mpd.conn)) {
fprintf(stderr, "MPD outputs: %s\n", mpd_connection_get_error_message(mpd.conn));
mpd_connection_clear_error(mpd.conn);
return 0;
}
str += snprintf(str, strend - str, " }}");
return str - buffer;
}
int mpd_put_current_song(char *buffer) {
int mpd_put_current_song(char *buffer)
{
char *cur = buffer;
const char *end = buffer + MAX_SIZE;
struct mpd_song *song;
song = mpd_run_current_song(mpd.conn);
if (song == NULL)
if(song == NULL)
return 0;
cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
{
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
}
if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
{
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
}
cur += json_emit_raw_str(cur, end - cur, "}}");
mpd_song_free(song);
@ -650,44 +390,32 @@ int mpd_put_current_song(char *buffer) {
return cur - buffer;
}
int mpd_put_queue(char *buffer, unsigned int offset) {
int mpd_put_queue(char *buffer, unsigned int offset)
{
char *cur = buffer;
const char *end = buffer + MAX_SIZE;
struct mpd_entity *entity;
unsigned long totalTime = 0;
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset + MAX_ELEMENTS_PER_PAGE))
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE))
RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
while ((entity = mpd_recv_entity(mpd.conn)) != NULL) {
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song;
unsigned int drtn;
if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
song = mpd_entity_get_song(entity);
drtn = mpd_song_get_duration(song);
cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
cur += json_emit_int(cur, end - cur, drtn);
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, "},");
totalTime += drtn;
}
mpd_entity_free(entity);
}
@ -695,13 +423,12 @@ int mpd_put_queue(char *buffer, unsigned int offset) {
/* remove last ',' */
cur--;
cur += json_emit_raw_str(cur, end - cur, "],\"totalTime\":");
cur += json_emit_int(cur, end - cur, totalTime);
cur += json_emit_raw_str(cur, end - cur, "}");
cur += json_emit_raw_str(cur, end - cur, "]}");
return cur - buffer;
}
int mpd_put_browse(char *buffer, char *path, unsigned int offset) {
int mpd_put_browse(char *buffer, char *path, unsigned int offset)
{
char *cur = buffer;
const char *end = buffer + MAX_SIZE;
struct mpd_entity *entity;
@ -710,22 +437,25 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset) {
if (!mpd_send_list_meta(mpd.conn, path))
RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
while ((entity = mpd_recv_entity(mpd.conn)) != NULL) {
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song;
const struct mpd_directory *dir;
const struct mpd_playlist *pl;
if (offset > entity_count) {
if(offset > entity_count)
{
mpd_entity_free(entity);
entity_count++;
continue;
} else if (offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count) {
}
else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
{
mpd_entity_free(entity);
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":");
cur += json_emit_int(cur, end - cur, entity_count);
cur += json_emit_raw_str(cur, end - cur, "} ");
cur += json_emit_raw_str(cur, end - cur, "} ");
break;
}
@ -737,10 +467,6 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset) {
song = mpd_entity_get_song(entity);
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
@ -780,41 +506,35 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset) {
return cur - buffer;
}
int mpd_search(char *buffer, char *searchstr) {
int mpd_search(char *buffer, char *searchstr)
{
int i = 0;
char *cur = buffer;
const char *end = buffer + MAX_SIZE;
struct mpd_song *song;
if (mpd_search_db_songs(mpd.conn, false) == false)
if(mpd_search_db_songs(mpd.conn, false) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_db_songs");
else if (mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false)
else if(mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_add_any_tag_constraint");
else if (mpd_search_commit(mpd.conn) == false)
else if(mpd_search_commit(mpd.conn) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_commit");
else {
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ ");
while ((song = mpd_recv_song(mpd.conn)) != NULL) {
while((song = mpd_recv_song(mpd.conn)) != NULL) {
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
cur += json_emit_raw_str(cur, end - cur, "},");
mpd_song_free(song);
/* Maximum results */
if (i++ >= 300) {
if(i++ >= 300)
{
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
break;
}
@ -828,7 +548,9 @@ int mpd_search(char *buffer, char *searchstr) {
return cur - buffer;
}
void mpd_disconnect() {
void mpd_disconnect()
{
mpd.conn_state = MPD_DISCONNECT;
mpd_poll(NULL);
}

View File

@ -1,7 +1,7 @@
/* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
@ -15,65 +15,56 @@
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __MPD_CLIENT_H__
#define __MPD_CLIENT_H__
#include "mongoose.h"
#define RETURN_ERROR_AND_RECOVER(X) \
do { \
fprintf(stderr, "MPD X: %s\n", mpd_connection_get_error_message(mpd.conn)); \
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", \
mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
return cur - buffer; \
} while (0)
#define RETURN_ERROR_AND_RECOVER(X) do { \
fprintf(stderr, "MPD X: %s\n", mpd_connection_get_error_message(mpd.conn)); \
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", \
mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
return cur - buffer; \
} while(0)
#define MAX_SIZE 1024 * 100
#define MAX_ELEMENTS_PER_PAGE 512
#define WSS_AUTH_TOKEN_SIZE 50
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
#define MPD_CMDS(X) \
X(MPD_API_GET_QUEUE) \
X(MPD_API_GET_BROWSE) \
X(MPD_API_GET_MPDHOST) \
X(MPD_API_ADD_TRACK) \
X(MPD_API_ADD_PLAY_TRACK) \
X(MPD_API_ADD_PLAYLIST) \
X(MPD_API_PLAY_TRACK) \
X(MPD_API_SAVE_QUEUE) \
X(MPD_API_RM_TRACK) \
X(MPD_API_RM_RANGE) \
X(MPD_API_RM_ALL) \
X(MPD_API_MOVE_TRACK) \
X(MPD_API_SEARCH) \
X(MPD_API_GET_CHANNELS) \
X(MPD_API_SEND_MESSAGE) \
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_GET_OUTPUTS) \
X(MPD_API_TOGGLE_OUTPUT) \
X(MPD_API_TOGGLE_RANDOM) \
X(MPD_API_TOGGLE_CONSUME) \
X(MPD_API_TOGGLE_SINGLE) \
X(MPD_API_TOGGLE_CROSSFADE) \
X(MPD_API_TOGGLE_REPEAT) \
X(MPD_API_AUTHORIZE)
#define MPD_CMDS(X) \
X(MPD_API_GET_QUEUE) \
X(MPD_API_GET_BROWSE) \
X(MPD_API_GET_MPDHOST) \
X(MPD_API_ADD_TRACK) \
X(MPD_API_ADD_PLAY_TRACK) \
X(MPD_API_ADD_PLAYLIST) \
X(MPD_API_PLAY_TRACK) \
X(MPD_API_RM_TRACK) \
X(MPD_API_RM_ALL) \
X(MPD_API_SEARCH) \
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)
enum mpd_cmd_ids { MPD_CMDS(GEN_ENUM) };
enum mpd_cmd_ids {
MPD_CMDS(GEN_ENUM)
};
enum mpd_conn_states {
MPD_DISCONNECTED,
@ -85,11 +76,8 @@ enum mpd_conn_states {
struct t_mpd {
int port;
int local_port;
char host[128];
char *password;
char *gpass;
char *wss_auth_token;
struct mpd_connection *conn;
enum mpd_conn_states conn_state;
@ -100,26 +88,21 @@ struct t_mpd {
int song_id;
unsigned queue_version;
};
extern struct t_mpd mpd;
} mpd;
struct t_mpd_client_session {
int song_id;
unsigned queue_version;
int authorized;
char *auth_token;
};
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_outputs(char *buffer, int putnames);
int mpd_put_channels(char *buffer);
int mpd_put_current_song(char *buffer);
int mpd_put_queue(char *buffer, unsigned int offset);
int mpd_put_browse(char *buffer, char *path, unsigned int offset);
int mpd_search(char *buffer, char *searchstr);
void mpd_disconnect();
#endif

View File

@ -1,7 +1,7 @@
/* ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
@ -16,178 +16,110 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <pthread.h>
#include "config.h"
#include "http_server.h"
#include "mongoose.h"
#include "http_server.h"
#include "mpd_client.h"
#include "config.h"
extern char *optarg;
int force_exit = 0;
void bye() {
void bye()
{
force_exit = 1;
}
static int server_callback(struct mg_connection *c, enum mg_event ev) {
int result = MG_FALSE;
FILE *fp = NULL;
switch (ev) {
case MG_CLOSE:
mpd_close_handler(c);
return MG_TRUE;
case MG_REQUEST:
if (c->is_websocket) {
c->content[c->content_len] = '\0';
if (c->content_len)
return callback_mpd(c);
else
return MG_TRUE;
} else
#ifdef WITH_DYNAMIC_ASSETS
return MG_FALSE;
#else
return callback_http(c);
#endif
case MG_AUTH:
// no auth for websockets since mobile safari does not support it
if ((mpd.gpass == NULL) || (c->is_websocket) ||
((mpd.local_port > 0) && (c->local_port == mpd.local_port)))
return MG_TRUE;
else {
if ((fp = fopen(mpd.gpass, "r")) != NULL) {
result = mg_authorize_digest(c, fp);
fclose(fp);
}
}
return result;
default:
return MG_FALSE;
static int server_callback(struct mg_connection *c) {
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 main(int argc, char **argv)
{
int n, option_index = 0;
struct mg_server *server = mg_create_server(NULL, server_callback);
struct mg_server *server = mg_create_server(NULL);
unsigned int current_timer = 0, last_timer = 0;
char *run_as_user = NULL;
char const *error_msg = NULL;
char *webport = "8080";
atexit(bye);
#ifdef WITH_DYNAMIC_ASSETS
mg_set_option(server, "document_root", SRC_PATH);
#endif
mg_set_option(server, "auth_domain", "ympd");
mg_set_option(server, "listening_port", "8080");
mpd.port = 6600;
mpd.local_port = 0;
mpd.gpass = NULL;
strcpy(mpd.host, "127.0.0.1");
/* clang-format off */
static struct option long_options[] = {
{"digest", required_argument, 0, 'D'},
{"host", required_argument, 0, 'h'},
{"port", required_argument, 0, 'p'},
{"localport", required_argument, 0, 'l'},
{"webport", required_argument, 0, 'w'},
{"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 0 },
{"mpdpass", required_argument, 0, 'm'},
{0, 0, 0, 0 }
};
/* clang-format on */
while ((n = getopt_long(argc, argv, "D:h:p:l:w:u:d:v:m", long_options, &option_index)) != -1) {
while((n = getopt_long(argc, argv, "h:p:w:u:v",
long_options, &option_index)) != -1) {
switch (n) {
case 'D':
mpd.gpass = strdup(optarg);
break;
case 'h':
strncpy(mpd.host, optarg, sizeof(mpd.host));
break;
case 'p':
mpd.port = atoi(optarg);
break;
case 'l':
mpd.local_port = atoi(optarg);
break;
case 'w':
webport = strdup(optarg);
mg_set_option(server, "listening_port", optarg);
break;
case 'u':
run_as_user = strdup(optarg);
break;
case 'm':
if (strlen(optarg) > 0)
mpd.password = strdup(optarg);
break;
case 'v':
fprintf(stdout,
"ympd %d.%d.%d\n"
fprintf(stdout, "ympd %d.%d.%d\n"
"Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
"built " __DATE__
" "__TIME__
" ("__VERSION__
")\n",
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH);
return EXIT_SUCCESS;
break;
default:
fprintf(stderr,
"Usage: %s [OPTION]...\n\n"
" -D, --digest <htdigest>\tpath to htdigest file for authorization\n"
" \t(realm ympd) [no authorization]\n"
fprintf(stderr, "Usage: %s [OPTION]...\n\n"
" -h, --host <host>\t\tconnect to mpd at host [localhost]\n"
" -p, --port <port>\t\tconnect to mpd at port [6600]\n"
" -l, --localport <port>\t\tskip authorization for local port\n"
" -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n"
" -u, --user <username>\t\tdrop priviliges to user after socket bind\n"
" -v, --version\t\t\tget version\n"
" -m, --mpdpass <password>\tspecifies the password to use when connecting "
"to mpd\n"
" --help\t\t\t\tthis help\n",
argv[0]);
" -V, --version\t\t\tget version\n"
" --help\t\t\t\tthis help\n"
, argv[0]);
return EXIT_FAILURE;
}
if (error_msg) {
fprintf(stderr, "Mongoose error: %s\n", error_msg);
return EXIT_FAILURE;
}
}
error_msg = mg_set_option(server, "listening_port", webport);
if (error_msg) {
fprintf(stderr, "Mongoose error: %s\n", error_msg);
return EXIT_FAILURE;
}
/* drop privilges at last to ensure proper port binding */
if (run_as_user != NULL) {
error_msg = mg_set_option(server, "run_as_user", run_as_user);
if(run_as_user != NULL)
{
mg_set_option(server, "run_as_user", run_as_user);
free(run_as_user);
if (error_msg) {
fprintf(stderr, "Mongoose error: %s\n", error_msg);
return EXIT_FAILURE;
}
}
mg_set_http_close_handler(server, mpd_close_handler);
mg_set_request_handler(server, server_callback);
while (!force_exit) {
mg_poll_server(server, 200);
current_timer = time(NULL);
if (current_timer - last_timer) {
current_timer = mg_poll_server(server, 200);
if(current_timer - last_timer)
{
last_timer = current_timer;
mpd_poll(server);
}

View File

@ -1,10 +0,0 @@
#!/bin/bash
repo="$(git rev-parse --show-toplevel)"
pushd "$repo"
prettier --write .
for i in http_server.c http_server.h json_encode.h mpd_client.c mpd_client.h ympd.c; do
clang-format -i -style=file "src/$i"
done
popd

View File

@ -1,65 +0,0 @@
#!/usr/bin/perl
# 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::Basename;
%mimetypes = (
js => 'application/javascript',
css => 'text/css',
ico => 'image/vnd.microsoft.icon',
woff => 'application/font-woff',
ttf => 'application/x-font-ttf',
eot => 'application/octet-stream',
svg => 'image/svg+xml',
html => 'text/html'
);
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;
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include "src/http_server.h"
static const struct embedded_file embedded_files[] = {
EOS
foreach my $i (0 .. $#ARGV) {
my ($ext) = $ARGV[$i] =~ /([^.]+)$/;
my $mime = $mimetypes{$ext};
$ARGV[$i] =~ s/htdocs//;
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;
}
EOS

5
ympd.1
View File

@ -1,6 +1,6 @@
.\" Manpage for ympd.
.\" Contact andy@ympd.org to correct errors or typos.
.TH man 1 "19 Oct 2014" "1.2.3" "ympd man page"
.TH man 8 "18 Mar 2014" "1.2" "ympd man page"
.SH NAME
ympd \- Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
.SH SYNOPSIS
@ -23,9 +23,6 @@ specifies the port for the webserver to listen to, defaults to 8080
\fB\-u\fR, \fB\-\-user username\fR
drop privileges to the provided username after socket binding
.TP
\fB\-m\fR, \fB\-\-mpdpass password\fR
specifies the password to use when connecting to mpd
.TP
\fB\-V\fR, \fB\-\-version\fR
print version and exit
.TP