Merge pull request #89 from jcorporation/decoupling

Merge decoupling branch into devel
This commit is contained in:
Jürgen Mang 2019-01-28 23:07:23 +01:00 committed by GitHub
commit 18edef3ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 3669 additions and 2280 deletions

View File

@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 2.6)
project (mympd C)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
set(CPACK_PACKAGE_VERSION_MAJOR "4")
set(CPACK_PACKAGE_VERSION_MINOR "8")
set(CPACK_PACKAGE_VERSION_MAJOR "5")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
if(CMAKE_BUILD_TYPE MATCHES RELEASE)
@ -14,32 +14,60 @@ else()
set(DEBUG "ON")
endif()
find_package(LibMPDClient REQUIRED)
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
MESSAGE("++ 64 bit architecture")
set(PKGARCH64 "ON")
else()
MESSAGE("++ 32 bit architecture")
set(PKGARCH64 "OFF")
endif()
configure_file(src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_INCLUDE_DIR})
find_package(LibMPDClient REQUIRED)
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
configure_file(src/global.h.in ${PROJECT_BINARY_DIR}/global.h)
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
include(CheckCSourceCompiles)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_ENABLE_SSL -D MG_ENABLE_IPV6 -D MG_DISABLE_MQTT -D MG_DISABLE_MQTT_BROKER -D MG_DISABLE_DNS_SERVER -D MG_DISABLE_COAP -D MG_DISABLE_HTTP_CGI -D MG_DISABLE_HTTP_SSI -D MG_DISABLE_HTTP_WEBDAV")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -D_FORTIFY_SOURCE=2 -fstack-protector -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null -fsanitize=return -fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=bounds-strict -fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=bool -fsanitize=enum -fsanitize=vptr -static-libasan")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -O1 -Wall -fstack-protector -D_FORTIFY_SOURCE=2 -pie -fPIE -DMG_ENABLE_SSL")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -fsanitize=address \
-fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=null -fsanitize=return -fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=bounds-strict \
-fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow \
-fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=bool -fsanitize=enum -fsanitize=vptr -static-libasan")
#for use with valgrind, disable above 2 set commands and enable the following set commands
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -DMG_ENABLE_SSL")
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb")
#compiler flags for mympd src
file(GLOB MYMPD_SRC_FILES "src/*.c")
set_property(SOURCE ${MYMPD_SRC_FILES} PROPERTY COMPILE_FLAGS "-Wextra -pedantic -Wformat=2 -Wno-unused-parameter -Wshadow -Wwrite-strings -Wstrict-prototypes -Wold-style-definition -Wredundant-decls -Wnested-externs -Wmissing-include-dirs")
#compiler flags for mongoose.c
set_property(SOURCE dist/src/mongoose/mongoose.c PROPERTY COMPILE_FLAGS "-Wno-format-truncation")
set (CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro -Wl,-z,now")
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_SSL)
set(SOURCES
src/mympd.c
src/main.c
src/global.c
src/mpd_client.c
src/web_server.c
src/mympd_api.c
src/list.c
src/validate.c
src/tiny_queue.c
dist/src/mongoose/mongoose.c
dist/src/frozen/frozen.c
dist/src/inih/ini.c
)
add_executable(mympd ${SOURCES})
target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${OPENSSL_LIBRARIES})
target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
install(TARGETS mympd DESTINATION bin)
install(FILES contrib/mympd.1 DESTINATION share/man/man1)

43
Dockerfile Normal file
View File

@ -0,0 +1,43 @@
FROM library/debian:9 as build
RUN apt-get update
RUN apt-get install git meson ninja-build gcc cmake libssl-dev -y
RUN apt-get install openjdk-8-jre-headless perl -y
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
RUN mkdir /libmpdclient-dist
RUN git clone https://github.com/MusicPlayerDaemon/libmpdclient.git
WORKDIR /libmpdclient
RUN meson . output
RUN ninja -C output
RUN ninja -C output install
RUN mesonconf output -Dprefix=/libmpdclient-dist
RUN ninja -C output
RUN ninja -C output install
WORKDIR /
RUN tar -czvf /libmpdclient-master.tar.gz -C /libmpdclient-dist .
COPY . /myMPD/
ENV MYMPD_INSTALL_PREFIX=/myMPD-dist/usr
RUN mkdir -p $MYMPD_INSTALL_PREFIX
WORKDIR /myMPD
RUN ./mkrelease.sh
WORKDIR /
RUN tar -czvf /mympd.tar.gz -C /myMPD-dist .
FROM library/debian:9-slim
ENV MYMPD_COVERIMAGENAME=folder.jpg
ENV MYMPD_MPDHOST=127.0.0.1
ENV MYMPD_MPDPORT=6600
ENV MYMPD_SSL=true
ENV MYMPD_LOGLEVEL=1
RUN apt-get update && apt-get install libssl-dev openssl -y
COPY --from=build /libmpdclient-master.tar.gz /
COPY --from=build /mympd.tar.gz /
COPY --from=build /myMPD/debian/postinst /
WORKDIR /
RUN tar xfv libmpdclient-master.tar.gz -C /
RUN tar xfv mympd.tar.gz -C /
RUN rm libmpdclient-master.tar.gz
RUN rm mympd.tar.gz
COPY contrib/docker/init.sh /
RUN chmod +x /init.sh
ENTRYPOINT ["/init.sh"]

View File

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

View File

@ -22,6 +22,7 @@ This fork provides a reworked ui based on Bootstrap 4, a modernized backend and
- Playlist management
- Advanced search (requires mpd >= 0.21.x and libmpdclient >= 2.17)
- Jukebox mode (add's random songs / albums from database or playlists to queue)
- AutoPlay - add song to (empty) queue and mpd starts playing
- Smart playlists and saved searches
- Play statistics and song voting (requires mpd stickers)
- Local coverart support (Albums and Streams)
@ -65,15 +66,16 @@ Unix Build Instructions
-----------------------
1. Install dependencies: cmake, libmpdclient (dev), OpenSSL (dev) and Java are available from all major distributions.
2. Build and install: ```cd /path/to/src; ./mkrelease.sh```.
2. Build and install: ```cd /path/to/src; ./mkrelease.sh``` (toplevel directory of myMPD tarball).
3. Link your mpd music directory to ```/usr/share/mympd/htdocs/library``` and put ```folder.jpg``` files in your album directories (mkrelease.sh tries to do this for you).
4. Configure your mpd with http stream output to use the local player.
Run Flags
Run
---------
```
Usage: ./mympd /etc/mypd/mympd.conf
```
The ```./mkrelease.sh``` script tries to install a systemd service file. If this failes you can copy the ```mympd.service``` file from ```/usr/share/mympd/``` to appropriate distribution specific location.
SSL
---
@ -85,6 +87,6 @@ SSL
Copyright
---------
ympd: 2013-2014 <andy@ndyk.de>
myMPD: 2018-2019 <mail@jcgames.de>
myMPD: 2018 <mail@jcgames.de>
ympd: 2013-2014 <andy@ndyk.de>

View File

@ -64,6 +64,7 @@ post_upgrade() {
[ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode
[ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist
[ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength
[ -f /var/lib/mympd/state/autoPlay ] || echo -n "false" > /var/lib/mympd/state/autoPlay
[ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage
[ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb
[ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase

11
contrib/docker/init.sh Normal file
View File

@ -0,0 +1,11 @@
#!/bin/sh
/postinst
sed -i "s#mpdhost = 127.0.0.1#mpdhost = $MYMPD_MPDHOST#g" /etc/mympd/mympd.conf
sed -i "s#mpdport = 6600#mpdport = $MYMPD_MPDPORT#g" /etc/mympd/mympd.conf
sed -i "s#ssl = true#ssl = $MYMPD_SSL#g" /etc/mympd/mympd.conf
sed -i "s#coverimagename = folder.jpg#coverimagename = $MYMPD_COVERIMAGENAME#g" /etc/mympd/mympd.conf
sed -i "s#loglevel = 1#loglevel = $MYMPD_LOGLEVEL#g" /etc/mympd/mympd.conf
mympd /etc/mympd/mympd.conf

View File

@ -4,7 +4,7 @@
# (c) 2018 Juergen Mang <mail@jcgames.de>
Name: myMPD
Version: 4.8.0
Version: 5.0.0
Release: 0
License: GPL-2.0
Group: Productivity/Multimedia/Sound/Players
@ -79,6 +79,7 @@ done
[ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode
[ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist
[ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength
[ -f /var/lib/mympd/state/autoPlay ] || echo -n "false" > /var/lib/mympd/state/autoPlay
[ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage
[ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb
[ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase

View File

@ -1,17 +1,16 @@
#myMPD config file
#Loglevel
#0 = quiet
#1 = default
#2 = verbose
#3 = debug
loglevel = 1
[mpd]
#Connection to mpd
mpdhost = 127.0.0.1
mpdport = 6600
#mpdpass =
#Port for mpd http stream
streamport = 8000
[webserver]
#Webserver options
webport = 80
@ -21,15 +20,21 @@ sslport = 443
sslcert = /etc/mympd/ssl/server.pem
sslkey = /etc/mympd/ssl/server.key
[mympd]
#Loglevel
#0 = quiet
#1 = default
#2 = verbose
#3 = debug
loglevel = 1
#myMPD user
user = mympd
#Enable local player, needs streamport or streamurl setting
localplayer = true
#Port for mpd http stream
streamport = 8000
#Manual streamurl, overwrites streamport
#streamurl = http://mpdhost:8000
@ -67,3 +72,7 @@ max_elements_per_page = 100
#Number of songs to keep in last played list
last_played_count = 20
[theme]
backgroundcolor: #888

5
debian/changelog vendored
View File

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

3
debian/postinst vendored
View File

@ -6,8 +6,6 @@ getent group mympd > /dev/null
getent passwd mympd > /dev/null
[ "$?" = "2" ] && useradd -r mympd -g mympd -d /var/lib/mympd -s /usr/sbin/nologin
echo "Trying to link musicdir to library"
if [ -f /etc/mpd.conf ]
then
@ -59,6 +57,7 @@ fi
[ -f /var/lib/mympd/state/jukeboxMode ] || echo -n "0" > /var/lib/mympd/state/jukeboxMode
[ -f /var/lib/mympd/state/jukeboxPlaylist ] || echo -n "Database" > /var/lib/mympd/state/jukeboxPlaylist
[ -f /var/lib/mympd/state/jukeboxQueueLength ] || echo -n "1" > /var/lib/mympd/state/jukeboxQueueLength
[ -f /var/lib/mympd/state/autoPlay ] || echo -n "false" > /var/lib/mympd/state/autoPlay
[ -f /var/lib/mympd/state/notificationPage ] || echo -n "true" > /var/lib/mympd/state/notificationPage
[ -f /var/lib/mympd/state/notificationWeb ] || echo -n "false" > /var/lib/mympd/state/notificationWeb
[ -f /var/lib/mympd/state/colsBrowseDatabase ] || echo -n '["Track","Title","Duration"]' > /var/lib/mympd/state/colsBrowseDatabase

View File

@ -1 +1,346 @@
:root{--mympd-coverimagesize:240px}html{position:relative;min-height:100%}body{padding-top:50px;padding-bottom:50px;background-color:#888}main{padding-top:10px}footer{position:absolute;bottom:0}button{overflow:hidden}.cardBodyPlayback{padding-bottom:0}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}[data-col=Pos],[data-col=Type],[data-col=Track],[data-col=Action]{width:30px}small{color:#aaa}.card{min-height:300px}.cardFooterPlayback{padding:0}.album-cover{background-size:cover;background-image:url("/assets/coverimage-loading.png");border:1px solid black;border-radius:5px;overflow:hidden;width:var(--mympd-coverimagesize);max-width:100%;height:var(--mympd-coverimagesize);background-color:#eee;float:left;margin-right:1.25rem;margin-bottom:1.25rem}.album-desc{min-width:240px;float:left;padding-bottom:1.25rem}.hide{display:none!important}.pull-right{float:right!important}.card-toolbar{margin-bottom:10px}.card-toolbar>div,.card-toolbar>form{margin-bottom:5px}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(/assets/MaterialIcons-Regular.woff2) format('woff2'),url(/assets/MaterialIcons-Regular.woff) format('woff'),url(/assets/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:18px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:top;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons-left{font-size:1rem;margin-left:-1em;vertical-align:middle}.material-icons-small{font-size:16px}.material-icons-small-left{font-size:1rem;margin-left:-1em}.color-darkgrey,.color-darkgrey:hover{color:#6c757d!important}#btn-outputs-block>button{margin-bottom:10px}#btn-outputs-block>button:last-child{margin-bottom:0}.card-body{overflow-x:hidden}.progressBarPlay{font-size:22px}#counter{cursor:text}#volumeBar{margin-top:2px;width:160px}.title-icon{float:left;margin-right:5px;font-size:1.8rem}.header-logo{font-size:2rem;float:left;margin-right:5px}.letters>button{width:28px;height:28px}.col-md{max-width:250px;min-width:250px}a.card-img-left{overflow:hidden;display:block;width:var(--mympd-coverimagesize);height:var(--mympd-coverimagesize);border-radius:5px;background-size:cover;background-image:url(/assets/coverimage-loading.png);margin-bottom:5px;cursor:pointer}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!important}button.active-fg-green{color:#28a745!important}button.active-fg-red{color:#bd2130!important}div#alertBox{position:fixed;top:50px;right:10px;width:80%;max-width:400px;z-index:1000;opacity:0;visibility:visible;transition:opacity .5s ease-in}div.alertBoxActive{opacity:1!important;visibility:visible!important;transition:opacity .5s ease-in}.popover-content{padding-top:4px;padding-bottom:4px}.opacity05{opacity:.5}caption{caption-side:top;font-size:120%;font-weight:bold;color:black}.dragover>td{border-top:2px solid #28a745}.dragover-th{border-left:2px solid #28a745}[draggable]{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element;cursor:pointer}@keyframes changewidth{0%{margin-left:-20px}to{margin-left:100%}}#updateDBprogress{width:20px}.updateDBprogressAnimate{animation-duration:2s;animation-name:changewidth;animation-iteration-count:infinite}.modal-body{overflow-x:hidden}.modal-body .album-cover{float:none}#BrowseDatabaseAlbumListCaption{margin-left:15px;margin-right:15px;width:100%}#BrowseDatabaseAlbumListCaption h2{display:inline}#BrowseDatabaseAlbumListCaption small{padding-top:.8rem}#menu-dbupdate{padding-left:1rem}div.key{border:1px solid #bbb;background-color:#eee;border-radius:2px;width:20px;heigth:20px;text-align:center}ol#searchCrumb{padding:.5rem}.nodropdown::after{content:none}
:root {
--mympd-coverimagesize: 240px;
--mympd-bacgkroundcolor: #888;
}
html {
position: relative;
min-height: 100%;
}
body {
padding-top: 4rem;
padding-bottom: 4rem;
background-color: var(--mympd-backgroundcolor, #888);
}
main {
padding-top: 1rem;
}
footer {
position: absolute;
bottom: 0;
}
button {
overflow: hidden;
}
.cardBodyPlayback {
padding-bottom:0px;
}
#BrowseBreadrumb {
overflow: auto;
white-space: nowrap;
}
#BrowseBreadcrumb > li > a {
cursor: pointer;
}
@media only screen and (max-width: 576px) {
.header-logo {
display:none !important;
}
}
.clickable {
cursor: pointer;
}
[data-col=Pos], [data-col=Type], [data-col=Track], [data-col=Action] {
width: 2rem;
}
small {
color: #aaa;
}
.card {
min-height: 300px;
}
.cardFooterPlayback {
padding: 0px;
}
.album-cover {
background-size: cover;
background-image: url("/assets/coverimage-loading.png");
border: 1px solid black;
border-radius: 5px;
overflow: hidden;
width: var(--mympd-coverimagesize, 250px);
max-width:100%;
height: var(--mympd-coverimagesize, 250px);
background-color: #eee;
float: left;
margin-right: 1.25rem;
margin-bottom: 1.25rem;
}
.album-desc {
min-width: 240px;
float: left;
padding-bottom:1.25rem;
}
.hide {
display: none !important;
}
.unvisible {
visibility: hidden;
}
.pull-right {
float: right !important;
}
.card-toolbar {
margin-bottom: 10px;
}
.card-toolbar > div, .card-toolbar > form {
margin-bottom: 5px;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(/assets/MaterialIcons-Regular.woff2) format('woff2'),
url(/assets/MaterialIcons-Regular.woff) format('woff'),
url(/assets/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 1.4rem; /* Preferred icon size */
display:inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
vertical-align: top;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
.material-icons-left {
font-size: 1rem;
margin-left: -1em;
vertical-align:middle;
}
.material-icons-small {
font-size: 1rem;
}
.material-icons-small-left {
font-size: 1rem;
margin-left: -1em;
}
.color-darkgrey, .color-darkgrey:hover {
color:#6c757d !important;
}
#btn-outputs-block > button {
margin-bottom:10px;
}
#btn-outputs-block > button:last-child {
margin-bottom:0px;
}
.card-body {
overflow-x:hidden;
}
.progressBarPlay {
font-size: 1.8rem;
}
#counter {
cursor: text;
}
#volumeBar {
margin-top:2px;
width:160px;
}
.title-icon {
float:left;
margin-right:5px;
font-size:1.8rem;
}
.header-logo {
font-size: 2rem;
float: left;
margin-right: 5px;
}
.letters > button {
width: 28px;
height: 28px;
}
.col-md {
max-width: 250px;
min-width: 250px;
}
a.card-img-left {
overflow: hidden;
display: block;
width: var(--mympd-coverimagesize);
height: var(--mympd-coverimagesize);
border-radius: 5px;
background-size: cover;
background-image: url(/assets/coverimage-loading.png);
margin-bottom: 5px;
cursor: pointer;
}
button.active {
color: #fff;
background-color: #28a745 !important;
border-color: #28a745 !important;
}
button.active-fg-green {
color: #28a745 !important;
}
button.active-fg-red {
color: #bd2130 !important;
}
div#alertBox {
position:fixed;
top: 4rem;
right:1rem;
width:80%;
max-width:400px;
z-index:1000;
opacity:0;
visibility:visible;
transition:opacity 0.5s ease-in;
}
div.alertBoxActive {
opacity:1 !important;
visibility:visible !important;
transition:opacity 0.5s ease-in;
}
.popover-content {
padding-top:4px;
padding-bottom:4px;
}
.opacity05 {
opacity:0.5;
}
caption {
caption-side: top;
font-size: 120%;
font-weight: bold;
color: black;
}
.dragover > td {
border-top:2px solid #28a745;
}
.dragover-th {
border-left:2px solid #28a745;
}
[draggable] {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
/* Required to make elements draggable in old WebKit */
-khtml-user-drag: element;
-webkit-user-drag: element;
cursor: pointer;
}
@keyframes changewidth {
from { margin-left: -20px; }
to { margin-left: 100%; }
}
#updateDBprogress {
width:20px;
}
.updateDBprogressAnimate {
animation-duration: 2s;
animation-name: changewidth;
animation-iteration-count: infinite;
}
.modal-body {
overflow-x: hidden;
}
.modal-body .album-cover {
float:none;
}
#BrowseDatabaseAlbumListCaption {
margin-left: 15px;
margin-right: 15px;
width: 100%;
}
#BrowseDatabaseAlbumListCaption h2 {
display: inline;
}
#BrowseDatabaseAlbumListCaption small {
padding-top: 0.8rem;
}
#menu-dbupdate {
padding-left:1rem;
}
div.key {
border: 1px solid #bbb;
background-color: #eee;
border-radius: 2px;
width: 20px;
height: 20px;
text-align: center;
}
ol#searchCrumb {
padding: .5rem;
}
.nodropdown::after {
content: none;
}

File diff suppressed because one or more lines are too long

View File

@ -5,62 +5,67 @@ $jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3");
$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var d=this;d instanceof String&&(d=String(d));var b=d.length;c=c||0;for(0>c&&(c=Math.max(c+b,0));c<b;c++){var f=d[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3");
$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6","es3");
$jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279<a)throw new RangeError("Invalid count value");a|=0;for(var d="";a;)if(a&1&&(d+=b),a>>>=1)b+=b;return d}},"es6","es3");
var socket,lastSong="",lastSongObj={},lastState,currentSong={},playstate="",settings={},alertTimeout,progressTimer,deferredPrompt,dragEl,playlistEl,app={apps:{Playback:{state:"0/-/",scrollPos:0},Queue:{active:"Current",tabs:{Current:{state:"0/any/",scrollPos:0},LastPlayed:{state:"0/any/",scrollPos:0}}},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/",scrollPos:0},Playlists:{active:"All",views:{All:{state:"0/-/",scrollPos:0},Detail:{state:"0/-/",scrollPos:0}}},Database:{active:"AlbumArtist",
views:{}}}},Search:{state:"0/any/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",search:"",scrollPos:0},last:{app:void 0,tab:void 0,view:void 0,filter:"",search:"",scrollPos:0}},domCache={};domCache.navbarBottomBtns=document.getElementById("navbar-bottom").getElementsByTagName("div");domCache.navbarBottomBtnsLen=domCache.navbarBottomBtns.length;domCache.cardHeaderBrowse=document.getElementById("cardHeaderBrowse").getElementsByTagName("a");
domCache.cardHeaderBrowseLen=domCache.cardHeaderBrowse.length;domCache.cardHeaderQueue=document.getElementById("cardHeaderQueue").getElementsByTagName("a");domCache.cardHeaderQueueLen=domCache.cardHeaderQueue.length;domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");domCache.volumeMenu=document.getElementById("volumeMenu");domCache.btnsPlay=document.getElementsByClassName("btnPlay");
domCache.btnsPlayLen=domCache.btnsPlay.length;domCache.btnPrev=document.getElementById("btnPrev");domCache.btnNext=document.getElementById("btnNext");domCache.progressBar=document.getElementById("progressBar");domCache.volumeBar=document.getElementById("volumeBar");domCache.outputs=document.getElementById("outputs");domCache.btnAdd=document.getElementById("nav-add2homescreen");domCache.currentCover=document.getElementById("currentCover");domCache.currentTitle=document.getElementById("currentTitle");
domCache.btnVoteUp=document.getElementById("btnVoteUp");domCache.btnVoteDown=document.getElementById("btnVoteDown");domCache.badgeQueueItems=document.getElementById("badgeQueueItems");domCache.searchstr=document.getElementById("searchstr");domCache.searchCrumb=document.getElementById("searchCrumb");
$jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279<a)throw new RangeError("Invalid count value");a|=0;for(var d="";a;)if(a&1&&(d+=b),a>>>=1)b+=b;return d}},"es6","es3");$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};
$jscomp.assign="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)$jscomp.owns(d,e)&&(a[e]=d[e])}return a};$jscomp.polyfill("Object.assign",function(a){return a||$jscomp.assign},"es6","es3");
var socket,lastSong="",lastSongObj={},lastState,currentSong={},playstate="",settingsLock=!1,settingsParsed=!1,settingsNew={},settings={},alertTimeout,progressTimer,deferredPrompt,dragEl,playlistEl,websocketConnected=!1,websocketTimer=null,appInited=!1,app={apps:{Playback:{state:"0/-/-/",scrollPos:0},Queue:{active:"Current",tabs:{Current:{state:"0/any/-/",scrollPos:0},LastPlayed:{state:"0/any/-/",scrollPos:0}}},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/-/",scrollPos:0},Playlists:{active:"All",
views:{All:{state:"0/-/-/",scrollPos:0},Detail:{state:"0/-/-/",scrollPos:0}}},Database:{active:"AlbumArtist",views:{}}}},Search:{state:"0/any/-/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",search:"",sort:"",scrollPos:0},last:{app:void 0,tab:void 0,view:void 0,filter:"",search:"",sort:"",scrollPos:0}},domCache={};domCache.navbarBottomBtns=document.getElementById("navbar-bottom").getElementsByTagName("div");domCache.navbarBottomBtnsLen=domCache.navbarBottomBtns.length;
domCache.cardHeaderBrowse=document.getElementById("cardHeaderBrowse").getElementsByTagName("a");domCache.cardHeaderBrowseLen=domCache.cardHeaderBrowse.length;domCache.cardHeaderQueue=document.getElementById("cardHeaderQueue").getElementsByTagName("a");domCache.cardHeaderQueueLen=domCache.cardHeaderQueue.length;domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");
domCache.volumeMenu=document.getElementById("volumeMenu");domCache.btnsPlay=document.getElementsByClassName("btnPlay");domCache.btnsPlayLen=domCache.btnsPlay.length;domCache.btnPrev=document.getElementById("btnPrev");domCache.btnNext=document.getElementById("btnNext");domCache.progressBar=document.getElementById("progressBar");domCache.volumeBar=document.getElementById("volumeBar");domCache.outputs=document.getElementById("outputs");domCache.btnAdd=document.getElementById("nav-add2homescreen");
domCache.currentCover=document.getElementById("currentCover");domCache.currentTitle=document.getElementById("currentTitle");domCache.btnVoteUp=document.getElementById("btnVoteUp");domCache.btnVoteDown=document.getElementById("btnVoteDown");domCache.badgeQueueItems=document.getElementById("badgeQueueItems");domCache.searchstr=document.getElementById("searchstr");domCache.searchCrumb=document.getElementById("searchCrumb");
var modalConnectionError=new Modal(document.getElementById("modalConnectionError"),{backdrop:"static",keyboard:!1}),modalSettings=new Modal(document.getElementById("modalSettings")),modalAbout=new Modal(document.getElementById("modalAbout")),modalSavequeue=new Modal(document.getElementById("modalSaveQueue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),modalAddToPlaylist=new Modal(document.getElementById("modalAddToPlaylist")),modalRenamePlaylist=new Modal(document.getElementById("modalRenamePlaylist")),
modalUpdateDB=new Modal(document.getElementById("modalUpdateDB")),modalSaveSmartPlaylist=new Modal(document.getElementById("modalSaveSmartPlaylist")),modalDeletePlaylist=new Modal(document.getElementById("modalDeletePlaylist")),modalHelp=new Modal(document.getElementById("modalHelp")),dropdownMainMenu,dropdownVolumeMenu=new Dropdown(document.getElementById("volumeMenu")),collapseDBupdate=new Collapse(document.getElementById("navDBupdate"));
modalUpdateDB=new Modal(document.getElementById("modalUpdateDB")),modalSaveSmartPlaylist=new Modal(document.getElementById("modalSaveSmartPlaylist")),modalDeletePlaylist=new Modal(document.getElementById("modalDeletePlaylist")),modalHelp=new Modal(document.getElementById("modalHelp")),modalAppInit=new Modal(document.getElementById("modalAppInit"),{backdrop:"static",keyboard:!1}),dropdownMainMenu,dropdownVolumeMenu=new Dropdown(document.getElementById("volumeMenu")),collapseDBupdate=new Collapse(document.getElementById("navDBupdate"));
function appPrepare(a){if(app.current.app!=app.last.app||app.current.tab!=app.last.tab||app.current.view!=app.last.view){for(var b=0;b<domCache.navbarBottomBtnsLen;b++)domCache.navbarBottomBtns[b].classList.remove("active");document.getElementById("cardPlayback").classList.add("hide");document.getElementById("cardQueue").classList.add("hide");document.getElementById("cardBrowse").classList.add("hide");document.getElementById("cardSearch").classList.add("hide");for(b=0;b<domCache.cardHeaderBrowseLen;b++)domCache.cardHeaderBrowse[b].classList.remove("active");
for(b=0;b<domCache.cardHeaderQueueLen;b++)domCache.cardHeaderQueue[b].classList.remove("active");document.getElementById("cardQueueCurrent").classList.add("hide");document.getElementById("cardQueueLastPlayed").classList.add("hide");document.getElementById("cardBrowsePlaylists").classList.add("hide");document.getElementById("cardBrowseDatabase").classList.add("hide");document.getElementById("cardBrowseFilesystem").classList.add("hide");document.getElementById("card"+app.current.app).classList.remove("hide");
document.getElementById("nav"+app.current.app)&&document.getElementById("nav"+app.current.app).classList.add("active");void 0!=app.current.tab&&(document.getElementById("card"+app.current.app+app.current.tab).classList.remove("hide"),document.getElementById("card"+app.current.app+"Nav"+app.current.tab).classList.add("active"));scrollTo(a)}(a=document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+(void 0==app.current.view?"":app.current.view)+"List"))&&a.classList.add("opacity05")}
function appGoto(a,b,c,d){var e=document.body.scrollTop?document.body.scrollTop:document.documentElement.scrollTop;void 0!=app.apps[app.current.app].scrollPos?app.apps[app.current.app].scrollPos=e:void 0!=app.apps[app.current.app].tabs[app.current.tab].scrollPos?app.apps[app.current.app].tabs[app.current.tab].scrollPos=e:void 0!=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos=e);app.apps[a].tabs?
(void 0==b&&(b=app.apps[a].active),app.apps[a].tabs[b].views?(void 0==c&&(c=app.apps[a].tabs[b].active),a="/"+a+"/"+b+"/"+c+"!"+(void 0==d?app.apps[a].tabs[b].views[c].state:d)):a="/"+a+"/"+b+"!"+(void 0==d?app.apps[a].tabs[b].state:d)):a="/"+a+"!"+(void 0==d?app.apps[a].state:d);location.hash=a}
function appRoute(){var a;if(a=decodeURI(location.hash).match(/^#\/(\w+)\/?(\w+)?\/?(\w+)?!((\d+)\/([^\/]+)\/(.*))$/)){app.current.app=a[1];app.current.tab=a[2];app.current.view=a[3];app.apps[app.current.app].state?(app.apps[app.current.app].state=a[4],app.current.scrollPos=app.apps[app.current.app].scrollPos):app.apps[app.current.app].tabs[app.current.tab].state?(app.apps[app.current.app].tabs[app.current.tab].state=a[4],app.apps[app.current.app].active=app.current.tab,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].scrollPos):
app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state=a[4],app.apps[app.current.app].active=app.current.tab,app.apps[app.current.app].tabs[app.current.tab].active=app.current.view,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos);app.current.page=parseInt(a[5]);app.current.filter=a[6];app.current.search=a[7];appPrepare(app.current.scrollPos);
if("Playback"==app.current.app)sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);else if("Queue"==app.current.app&&"Current"==app.current.tab)selectTag("searchqueuetags","searchqueuetagsdesc",app.current.filter),getQueue();else if("Queue"==app.current.app&&"LastPlayed"==app.current.tab)sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"All"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_LIST",
data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Database"==app.current.tab)""!=app.current.search?(sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_LIST",
data:{offset:app.current.page,filter:app.current.filter,search:app.current.search,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter")):(sendAPI({cmd:"MPD_API_DATABASE_TAG_LIST",data:{offset:app.current.page,filter:app.current.filter,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter"),selectTag("BrowseDatabaseByTagDropdown","btnBrowseDatabaseByTag",app.current.view));else if("Browse"==app.current.app&&"Filesystem"==app.current.tab){sendAPI({cmd:"MPD_API_DATABASE_FILESYSTEM_LIST",
data:{offset:app.current.page,path:app.current.search?app.current.search:"/",filter:app.current.filter}},parseFilesystem);app.current.search?(document.getElementById("BrowseFilesystemAddAllSongs").removeAttribute("disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("BrowseFilesystemAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").setAttribute("disabled","disabled"));var b=
'<li class="breadcrumb-item"><a data-uri="">root</a></li>',c=app.current.search.split("/"),d=c.length,e="";for(a=0;a<d;a++){if(d-1==a){b+='<li class="breadcrumb-item active">'+c[a]+"</li>";break}e+=c[a];b+='<li class="breadcrumb-item"><a data-uri="'+e+'">'+c[a]+"</a></li>";e+="/"}a=document.getElementById("BrowseBreadcrumb");a.innerHTML=b;b=a.getElementsByTagName("a");c=b.length;for(a=0;a<c;a++)b[a].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+
this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"==app.current.app){domCache.searchstr.focus();if(settings.featAdvsearch){b="";c=app.current.search.substring(1,app.current.search.length-1).split(" AND ");for(a=0;a<c.length-1;a++)d=c[a].substring(1,c[a].length-1),b+='<button data-filter="'+encodeURI(d)+'" class="btn btn-light mr-2">'+d+'<span class="ml-2 badge badge-secondary">&times</span></button>';domCache.searchCrumb.innerHTML=b;""==domCache.searchstr.value&&
1<=c.length&&(a=c[c.length-1].substring(1,c[c.length-1].length-1),b=a.substring(a.indexOf("'")+1,a.length-1),domCache.searchstr.value!=b&&(domCache.searchCrumb.innerHTML+='<button data-filter="'+encodeURI(a)+'" class="btn btn-light mr-2">'+a+'<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>'),a=a.substring(a.indexOf(" ")+1),a=a.substring(0,a.indexOf(" ")),""==a&&(a="contains"),document.getElementById("searchMatch").value=a)}else""==domCache.searchstr.value&&""!=app.current.search&&
(domCache.searchstr.value=app.current.search);app.last.app!=app.current.app&&""!=app.current.search&&(a=settings["cols"+app.current.app].length,a--,document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="'+a+'">Searching...</td></tr>');2<=domCache.searchstr.value.length||0<domCache.searchCrumb.children.length?settings.featAdvsearch?(a=document.getElementById("SearchList").getAttribute("data-sort"),b=!1,""==
a?(a=settings.tags.includes("Title")?"Title":"",document.getElementById("SearchList").setAttribute("data-sort",a)):0==a.indexOf("-")&&(b=!0,a=a.substring(1)),sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:"",offset:app.current.page,sort:a,sortdesc:b,expression:app.current.search}},parseSearch)):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:"",offset:app.current.page,filter:app.current.filter,searchstr:app.current.search}},parseSearch):(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML=
"",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("cardFooterSearch").innerText="",document.getElementById("SearchList").classList.remove("opacity05"),setPagination(0,0));selectTag("searchtags","searchtagsdesc",app.current.filter)}else appGoto("Playback");app.last.app=app.current.app;app.last.tab=
app.current.tab;app.last.view=app.current.view}else appGoto("Playback")}
function appInit(){webSocketConnect();getSettings();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);domCache.volumeBar.value=0;document.getElementById("btnChVolumeDown").addEventListener("click",function(a){a.stopPropagation()},!1);document.getElementById("btnChVolumeUp").addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",
data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=0;domCache.progressBar.addEventListener("change",function(a){currentSong&&0<=currentSong.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_SEEK",data:{songid:currentSong.currentSongId,seek:Math.ceil(domCache.progressBar.value/1E3*currentSong.totalTime)}})},!1);document.getElementById("navDBupdate").addEventListener("click",function(a){a.stopPropagation();a.preventDefault();a=this.getElementsByTagName("span")[0];a.innerText="keyboard_arrow_right"==
a.innerText?"keyboard_arrow_down":"keyboard_arrow_right"},!1);document.getElementById("volumeMenu").parentNode.addEventListener("show.bs.dropdown",function(){sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs)});document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_DATABASE_STATS"},parseStats)});document.getElementById("modalAddToPlaylist").addEventListener("shown.bs.modal",function(){document.getElementById("addStreamFrm").classList.contains("hide")?
document.getElementById("addToPlaylistPlaylist").focus():document.getElementById("streamUrl").focus()});document.getElementById("modalHelp").addEventListener("show.bs.modal",function(){var a="",b;for(b in keymap)if(void 0==keymap[b].req||1==settings[keymap[b].req])a+='<tr><td><div class="key'+(keymap[b].key&&1<keymap[b].key.length?" material-icons material-icons-small":"")+'">'+(void 0!=keymap[b].key?keymap[b].key:b)+"</div></td><td>"+keymap[b].desc+"</td></tr>";document.getElementById("tbodyShortcuts").innerHTML=
a});document.getElementById("modalUpdateDB").addEventListener("hidden.bs.modal",function(){document.getElementById("updateDBprogress").classList.remove("updateDBprogressAnimate")});document.getElementById("modalSaveQueue").addEventListener("shown.bs.modal",function(){var a=document.getElementById("saveQueueName");a.focus();a.value="";a.classList.remove("is-invalid");document.getElementById("saveQueueFrm").classList.remove("was-validated")});document.getElementById("modalSettings").addEventListener("shown.bs.modal",
function(){getSettings();document.getElementById("settingsFrm").classList.remove("was-validated");document.getElementById("inputCrossfade").classList.remove("is-invalid");document.getElementById("inputMixrampdb").classList.remove("is-invalid");document.getElementById("inputMixrampdelay").classList.remove("is-invalid")});document.getElementById("selectJukeboxMode").addEventListener("change",function(){var a=this.options[this.selectedIndex].value;0==a||2==a?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled",
"disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled","disabled")):1==a&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"))});document.getElementById("addToPlaylistPlaylist").addEventListener("change",function(a){"New Playlist"==this.options[this.selectedIndex].text?(document.getElementById("addToPlaylistNewPlaylistDiv").classList.remove("hide"),document.getElementById("addToPlaylistNewPlaylist").focus()):
document.getElementById("addToPlaylistNewPlaylistDiv").classList.add("hide")},!1);addFilterLetter("BrowseFilesystemFilterLetters");addFilterLetter("BrowseDatabaseFilterLetters");addFilterLetter("BrowsePlaylistsFilterLetters");document.getElementById("syscmds").addEventListener("click",function(a){"A"==a.target.nodeName&&parseCmd(a,a.target.getAttribute("data-href"))},!1);for(var a=document.querySelectorAll("[data-href]"),b=a.length,c=0;c<b;c++)a[c].classList.add("clickable"),a[c].addEventListener("click",
function(a){parseCmd(a,this.getAttribute("data-href"))},!1);a=document.getElementsByClassName("pages");b=a.length;for(c=0;c<b;c++)a[c].addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&gotoPage(a.target.getAttribute("data-page"))},!1);document.getElementById("cardPlaybackTags").addEventListener("click",function(a){"H4"==a.target.nodeName&&gotoBrowse(a.target)},!1);document.getElementById("modalSongDetails").getElementsByTagName("tbody")[0].addEventListener("click",function(a){"A"==
a.target.nodeName?void 0!=a.target.parentNode.getAttribute("data-tag")&&(modalSongDetails.hide(),a.preventDefault(),gotoBrowse(a.target)):"BUTTON"==a.target.nodeName&&a.target.getAttribute("data-href")&&parseCmd(a,a.target.getAttribute("data-href"))},!1);document.getElementById("outputs").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&a.stopPropagation();sendAPI({cmd:"MPD_API_PLAYER_TOGGLE_OUTPUT",data:{output:a.target.getAttribute("data-output-id"),state:a.target.classList.contains("active")?
0:1}});toggleBtn(a.target.id)},!1);document.getElementById("QueueCurrentList").addEventListener("click",function(a){"TD"==a.target.nodeName?sendAPI({cmd:"MPD_API_PLAYER_PLAY_TRACK",data:{track:a.target.parentNode.getAttribute("data-trackid")}}):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("QueueLastPlayedList").addEventListener("click",function(a){"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemList").addEventListener("click",
function(a){if("TD"==a.target.nodeName)switch(a.target.parentNode.getAttribute("data-type")){case "dir":appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+decodeURI(a.target.parentNode.getAttribute("data-uri")));break;case "song":appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"));break;case "plist":appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"))}else"A"==
a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsAllList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsDetailList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),
a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseDatabaseTagList").addEventListener("click",function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database",app.current.view,"0/-/"+a.target.parentNode.getAttribute("data-uri"))},!1);document.getElementById("SearchList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):
"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowse():"Add all to playlist"==a.target.innerText&&showAddToPlaylist(app.current.search))},!1);document.getElementById("searchAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromSearchPlist("queue"):
"Add all to playlist"==a.target.innerText?showAddToPlaylist("SEARCH"):"Save as smart playlist"==a.target.innerText&&saveSearchAsSmartPlaylist())},!1);document.getElementById("BrowseDatabaseAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowseDatabasePlist("queue"):"Add all to playlist"==a.target.innerText&&showAddToPlaylist("DATABASE"))},!1);document.getElementById("searchtags").addEventListener("click",function(a){"BUTTON"==
a.target.nodeName&&(app.current.filter=a.target.getAttribute("data-tag"),search(domCache.searchstr.value))},!1);document.getElementById("searchqueuestr").addEventListener("keyup",function(a){"Escape"==a.key?this.blur():appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("searchqueuetags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+
"/"+a.target.getAttribute("data-tag")+"/"+app.current.search)},!1);a="QueueCurrentColsDropdown BrowseFilesystemColsDropdown SearchColsDropdown BrowsePlaylistsDetailColsDropdown BrowseDatabaseColsDropdown PlaybackColsDropdown QueueLastPlayedColsDropdown".split(" ");for(c=0;c<a.length;c++)document.getElementById(a[c]).addEventListener("click",function(a){"INPUT"==a.target.nodeName&&a.stopPropagation()},!1);document.getElementById("search").addEventListener("submit",function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",
function(){return!1},!1);domCache.searchstr.addEventListener("keyup",function(a){if("Escape"==a.key)this.blur();else if("Enter"==a.key&&settings.featAdvsearch)if(""!=this.value){a=document.getElementById("searchMatch");var b=document.createElement("button");b.classList.add("btn","btn-light","mr-2");b.setAttribute("data-filter",encodeURI(app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+"'"));b.innerHTML=app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+
'\'<span class="ml-2 badge badge-secondary">&times;</span>';this.value="";domCache.searchCrumb.appendChild(b)}else search(this.value);else search(this.value)},!1);domCache.searchCrumb.addEventListener("click",function(a){a.preventDefault();a.stopPropagation();if("SPAN"==a.target.nodeName)a.target.parentNode.remove(),search("");else if("BUTTON"==a.target.nodeName){var b=decodeURI(a.target.getAttribute("data-filter"));domCache.searchstr.value=b.substring(b.indexOf("'")+1,b.length-1);var c=b.substring(0,
b.indexOf(" "));selectTag("searchtags","searchtagsdesc",c);b=b.substring(b.indexOf(" ")+1);b=b.substring(0,b.indexOf(" "));document.getElementById("searchMatch").value=b;a.target.remove();search(domCache.searchstr.value)}},!1);document.getElementById("searchMatch").addEventListener("change",function(a){search(domCache.searchstr.value)},!1);document.getElementById("SearchList").getElementsByTagName("tr")[0].addEventListener("click",function(a){if(settings.featAdvsearch&&"TH"==a.target.nodeName){var b=
a.target.getAttribute("data-col");if("Duration"!=b){var c=document.getElementById("SearchList").getAttribute("data-sort"),d=!0;if(c==b||c=="-"+b)0==c.indexOf("-")?(d=!0,c.substring(1)):d=!1;0==d?(c="-"+b,d=!0):(d=!1,c=b);for(var h=document.getElementById("SearchList").getElementsByClassName("sort-dir"),l=0;l<h.length;l++)h[l].remove();document.getElementById("SearchList").setAttribute("data-sort",c);a.target.innerHTML=b+'<span class="sort-dir material-icons pull-right">'+(1==d?"arrow_drop_up":"arrow_drop_down")+
"</span>";appRoute()}}},!1);document.getElementById("BrowseDatabaseByTagDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,a.target.getAttribute("data-tag"),"0/"+app.current.filter+"/"+app.current.search)},!1);document.getElementsByTagName("body")[0].addEventListener("click",function(a){hideMenu()},!1);dragAndDropTable("QueueCurrentList");dragAndDropTable("BrowsePlaylistsDetailList");dragAndDropTableHeader("QueueCurrent");dragAndDropTableHeader("QueueLastPlayed");
dragAndDropTableHeader("Search");dragAndDropTableHeader("BrowseFilesystem");dragAndDropTableHeader("BrowsePlaylistsDetail");window.addEventListener("hashchange",appRoute,!1);window.addEventListener("focus",function(){sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName&&"SELECT"!=a.target.tagName&&!a.ctrlKey&&!a.altKey){var b=keymap[a.key];b&&"function"===typeof window[b.cmd]&&(void 0==keymap[a.key].req||1==settings[keymap[a.key].req])&&
parseCmd(a,b)}},!1);"serviceWorker"in navigator&&"https"==document.URL.substring(0,5)&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.min.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",a.scope);a.update()},function(a){console.log("ServiceWorker registration failed: ",a)})});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a});window.addEventListener("beforeinstallprompt",
function(a){a.preventDefault();deferredPrompt=a;domCache.btnAdd.classList.remove("hide")});domCache.btnAdd.addEventListener("click",function(a){domCache.btnAdd.classList.add("hide");deferredPrompt.prompt();deferredPrompt.userChoice.then(function(a){"accepted"===a.outcome?console.log("User accepted the A2HS prompt"):console.log("User dismissed the A2HS prompt");deferredPrompt=null})});window.addEventListener("appinstalled",function(a){console.log("myMPD installed as app")})}
function appRoute(){if(0==settingsParsed)appInitStart();else{var a;if(a=decodeURI(location.hash).match(/^#\/(\w+)\/?(\w+)?\/?(\w+)?!((\d+)\/([^\/]+)\/([^\/]+)\/(.*))$/)){app.current.app=a[1];app.current.tab=a[2];app.current.view=a[3];app.apps[app.current.app].state?(app.apps[app.current.app].state=a[4],app.current.scrollPos=app.apps[app.current.app].scrollPos):app.apps[app.current.app].tabs[app.current.tab].state?(app.apps[app.current.app].tabs[app.current.tab].state=a[4],app.apps[app.current.app].active=
app.current.tab,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].scrollPos):app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state&&(app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].state=a[4],app.apps[app.current.app].active=app.current.tab,app.apps[app.current.app].tabs[app.current.tab].active=app.current.view,app.current.scrollPos=app.apps[app.current.app].tabs[app.current.tab].views[app.current.view].scrollPos);app.current.page=
parseInt(a[5]);app.current.filter=a[6];app.current.sort=a[7];app.current.search=a[8];appPrepare(app.current.scrollPos);if("Playback"==app.current.app)sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);else if("Queue"==app.current.app&&"Current"==app.current.tab)selectTag("searchqueuetags","searchqueuetagsdesc",app.current.filter),getQueue();else if("Queue"==app.current.app&&"LastPlayed"==app.current.tab)sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);
else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"All"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");else if("Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view)sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists),doSetFilterLetter("BrowsePlaylistsFilter");
else if("Browse"==app.current.app&&"Database"==app.current.tab)""!=app.current.search?(sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_LIST",data:{offset:app.current.page,filter:app.current.filter,search:app.current.search,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter")):(sendAPI({cmd:"MPD_API_DATABASE_TAG_LIST",data:{offset:app.current.page,filter:app.current.filter,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter"),selectTag("BrowseDatabaseByTagDropdown",
"btnBrowseDatabaseByTag",app.current.view));else if("Browse"==app.current.app&&"Filesystem"==app.current.tab){sendAPI({cmd:"MPD_API_DATABASE_FILESYSTEM_LIST",data:{offset:app.current.page,path:app.current.search?app.current.search:"/",filter:app.current.filter}},parseFilesystem);app.current.search?(document.getElementById("BrowseFilesystemAddAllSongs").removeAttribute("disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("BrowseFilesystemAddAllSongs").setAttribute("disabled",
"disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").setAttribute("disabled","disabled"));var b='<li class="breadcrumb-item"><a data-uri="">root</a></li>',c=app.current.search.split("/"),d=c.length,e="";for(a=0;a<d;a++){if(d-1==a){b+='<li class="breadcrumb-item active">'+c[a]+"</li>";break}e+=c[a];b+='<li class="breadcrumb-item"><a data-uri="'+e+'">'+c[a]+"</a></li>";e+="/"}a=document.getElementById("BrowseBreadcrumb");a.innerHTML=b;b=a.getElementsByTagName("a");c=b.length;for(a=
0;a<c;a++)b[a].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+app.current.sort+"/"+this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"==app.current.app){domCache.searchstr.focus();if(settings.featAdvsearch){b="";c=app.current.search.substring(1,app.current.search.length-1).split(" AND ");for(a=0;a<c.length-1;a++)d=c[a].substring(1,c[a].length-1),b+='<button data-filter="'+encodeURI(d)+'" class="btn btn-light mr-2">'+
d+'<span class="ml-2 badge badge-secondary">&times</span></button>';domCache.searchCrumb.innerHTML=b;""==domCache.searchstr.value&&1<=c.length&&(a=c[c.length-1].substring(1,c[c.length-1].length-1),b=a.substring(a.indexOf("'")+1,a.length-1),domCache.searchstr.value!=b&&(domCache.searchCrumb.innerHTML+='<button data-filter="'+encodeURI(a)+'" class="btn btn-light mr-2">'+a+'<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>'),a=a.substring(a.indexOf(" ")+1),a=a.substring(0,a.indexOf(" ")),
""==a&&(a="contains"),document.getElementById("searchMatch").value=a)}else""==domCache.searchstr.value&&""!=app.current.search&&(domCache.searchstr.value=app.current.search);app.last.app!=app.current.app&&""!=app.current.search&&(a=settings["cols"+app.current.app].length,a--,document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="'+a+'">Searching...</td></tr>');2<=domCache.searchstr.value.length||0<domCache.searchCrumb.children.length?
settings.featAdvsearch?(a=app.current.sort,b=!1,"-"==a?(a=settings.tags.includes("Title")?"Title":"-",document.getElementById("SearchList").setAttribute("data-sort",a)):0==a.indexOf("-")&&(b=!0,a=a.substring(1)),sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:"",offset:app.current.page,sort:a,sortdesc:b,expression:app.current.search}},parseSearch)):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:"",offset:app.current.page,filter:app.current.filter,searchstr:app.current.search}},parseSearch):
(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML="",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("cardFooterSearch").innerText="",document.getElementById("SearchList").classList.remove("opacity05"),setPagination(0,0));selectTag("searchtags","searchtagsdesc",app.current.filter)}else appGoto("Playback");
app.last.app=app.current.app;app.last.tab=app.current.tab;app.last.view=app.current.view}else appGoto("Playback")}}
function appInitStart(){appInited=!1;document.getElementsByTagName("header")[0].classList.add("hide");document.getElementsByTagName("main")[0].classList.add("hide");document.getElementsByTagName("footer")[0].classList.add("hide");document.getElementById("appInitSettings").classList.add("unvisible");document.getElementById("appInitWebsocket").classList.add("unvisible");document.getElementById("appInitApply").classList.add("unvisible");modalAppInit.show();getSettings();appInitWait()}
function appInitWait(){setTimeout(function(){1==settingsParsed&&1==websocketConnected?(document.getElementById("appInitWebsocket").classList.remove("unvisible"),appInit(),document.getElementById("appInitApply").classList.remove("unvisible"),document.getElementsByTagName("header")[0].classList.remove("hide"),document.getElementsByTagName("main")[0].classList.remove("hide"),document.getElementsByTagName("footer")[0].classList.remove("hide"),modalAppInit.hide(),appInited=!0):(1==settingsParsed&&(document.getElementById("appInitSettings").classList.remove("unvisible"),
webSocketConnect()),appInitWait())},500)}
function appInit(){domCache.volumeBar.value=0;document.getElementById("btnChVolumeDown").addEventListener("click",function(a){a.stopPropagation()},!1);document.getElementById("btnChVolumeUp").addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=
0;domCache.progressBar.addEventListener("change",function(a){currentSong&&0<=currentSong.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_SEEK",data:{songid:currentSong.currentSongId,seek:Math.ceil(domCache.progressBar.value/1E3*currentSong.totalTime)}})},!1);document.getElementById("navDBupdate").addEventListener("click",function(a){a.stopPropagation();a.preventDefault();a=this.getElementsByTagName("span")[0];a.innerText="keyboard_arrow_right"==a.innerText?"keyboard_arrow_down":"keyboard_arrow_right"},
!1);document.getElementById("volumeMenu").parentNode.addEventListener("show.bs.dropdown",function(){sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs)});document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_DATABASE_STATS"},parseStats)});document.getElementById("modalAddToPlaylist").addEventListener("shown.bs.modal",function(){document.getElementById("addStreamFrm").classList.contains("hide")?document.getElementById("addToPlaylistPlaylist").focus():
document.getElementById("streamUrl").focus()});document.getElementById("modalHelp").addEventListener("show.bs.modal",function(){var a="",b;for(b in keymap)if(void 0==keymap[b].req||1==settings[keymap[b].req])a+='<tr><td><div class="key'+(keymap[b].key&&1<keymap[b].key.length?" material-icons material-icons-small":"")+'">'+(void 0!=keymap[b].key?keymap[b].key:b)+"</div></td><td>"+keymap[b].desc+"</td></tr>";document.getElementById("tbodyShortcuts").innerHTML=a});document.getElementById("modalUpdateDB").addEventListener("hidden.bs.modal",
function(){document.getElementById("updateDBprogress").classList.remove("updateDBprogressAnimate")});document.getElementById("modalSaveQueue").addEventListener("shown.bs.modal",function(){var a=document.getElementById("saveQueueName");a.focus();a.value="";a.classList.remove("is-invalid");document.getElementById("saveQueueFrm").classList.remove("was-validated")});document.getElementById("modalSettings").addEventListener("shown.bs.modal",function(){getSettings();document.getElementById("settingsFrm").classList.remove("was-validated");
document.getElementById("inputCrossfade").classList.remove("is-invalid");document.getElementById("inputMixrampdb").classList.remove("is-invalid");document.getElementById("inputMixrampdelay").classList.remove("is-invalid")});document.getElementById("selectJukeboxMode").addEventListener("change",function(){var a=this.options[this.selectedIndex].value;0==a||2==a?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled","disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled",
"disabled")):1==a&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"))});document.getElementById("addToPlaylistPlaylist").addEventListener("change",function(a){"New Playlist"==this.options[this.selectedIndex].text?(document.getElementById("addToPlaylistNewPlaylistDiv").classList.remove("hide"),document.getElementById("addToPlaylistNewPlaylist").focus()):document.getElementById("addToPlaylistNewPlaylistDiv").classList.add("hide")},
!1);addFilterLetter("BrowseFilesystemFilterLetters");addFilterLetter("BrowseDatabaseFilterLetters");addFilterLetter("BrowsePlaylistsFilterLetters");document.getElementById("syscmds").addEventListener("click",function(a){"A"==a.target.nodeName&&parseCmd(a,a.target.getAttribute("data-href"))},!1);for(var a=document.querySelectorAll("[data-href]"),b=a.length,c=0;c<b;c++)a[c].classList.add("clickable"),a[c].addEventListener("click",function(a){parseCmd(a,this.getAttribute("data-href"))},!1);a=document.getElementsByClassName("pages");
b=a.length;for(c=0;c<b;c++)a[c].addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&gotoPage(a.target.getAttribute("data-page"))},!1);document.getElementById("cardPlaybackTags").addEventListener("click",function(a){"H4"==a.target.nodeName&&gotoBrowse(a.target)},!1);document.getElementById("modalSongDetails").getElementsByTagName("tbody")[0].addEventListener("click",function(a){"A"==a.target.nodeName?void 0!=a.target.parentNode.getAttribute("data-tag")&&(modalSongDetails.hide(),a.preventDefault(),
gotoBrowse(a.target)):"BUTTON"==a.target.nodeName&&a.target.getAttribute("data-href")&&parseCmd(a,a.target.getAttribute("data-href"))},!1);document.getElementById("outputs").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&a.stopPropagation();sendAPI({cmd:"MPD_API_PLAYER_TOGGLE_OUTPUT",data:{output:a.target.getAttribute("data-output-id"),state:a.target.classList.contains("active")?0:1}});toggleBtn(a.target.id)},!1);document.getElementById("QueueCurrentList").addEventListener("click",
function(a){"TD"==a.target.nodeName?sendAPI({cmd:"MPD_API_PLAYER_PLAY_TRACK",data:{track:a.target.parentNode.getAttribute("data-trackid")}}):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("QueueLastPlayedList").addEventListener("click",function(a){"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemList").addEventListener("click",function(a){if("TD"==a.target.nodeName)switch(a.target.parentNode.getAttribute("data-type")){case "dir":appGoto("Browse",
"Filesystem",void 0,"0/"+app.current.filter+"/"+app.current.sort+"/"+decodeURI(a.target.parentNode.getAttribute("data-uri")));break;case "song":appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"));break;case "plist":appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name"))}else"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsAllList").addEventListener("click",
function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlaylistsDetailList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("plist",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseDatabaseTagList").addEventListener("click",
function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database",app.current.view,"0/-/-/"+a.target.parentNode.getAttribute("data-uri"))},!1);document.getElementById("SearchList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemAddAllSongsDropdown").addEventListener("click",
function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowse():"Add all to playlist"==a.target.innerText&&showAddToPlaylist(app.current.search))},!1);document.getElementById("searchAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromSearchPlist("queue"):"Add all to playlist"==a.target.innerText?showAddToPlaylist("SEARCH"):"Save as smart playlist"==a.target.innerText&&saveSearchAsSmartPlaylist())},
!1);document.getElementById("BrowseDatabaseAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowseDatabasePlist("queue"):"Add all to playlist"==a.target.innerText&&showAddToPlaylist("DATABASE"))},!1);document.getElementById("searchtags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&(app.current.filter=a.target.getAttribute("data-tag"),search(domCache.searchstr.value))},!1);document.getElementById("searchqueuestr").addEventListener("keyup",
function(a){"Escape"==a.key?this.blur():appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+app.current.sort+"/"+this.value)},!1);document.getElementById("searchqueuetags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+a.target.getAttribute("data-tag")+"/"+app.current.sort+"/"+app.current.search)},!1);a="QueueCurrentColsDropdown BrowseFilesystemColsDropdown SearchColsDropdown BrowsePlaylistsDetailColsDropdown BrowseDatabaseColsDropdown PlaybackColsDropdown QueueLastPlayedColsDropdown".split(" ");
for(c=0;c<a.length;c++)document.getElementById(a[c]).addEventListener("click",function(a){"INPUT"==a.target.nodeName&&a.stopPropagation()},!1);document.getElementById("search").addEventListener("submit",function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",function(){return!1},!1);domCache.searchstr.addEventListener("keyup",function(a){if("Escape"==a.key)this.blur();else if("Enter"==a.key&&settings.featAdvsearch)if(""!=this.value){a=document.getElementById("searchMatch");
var b=document.createElement("button");b.classList.add("btn","btn-light","mr-2");b.setAttribute("data-filter",encodeURI(app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+"'"));b.innerHTML=app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+'\'<span class="ml-2 badge badge-secondary">&times;</span>';this.value="";domCache.searchCrumb.appendChild(b)}else search(this.value);else search(this.value)},!1);domCache.searchCrumb.addEventListener("click",function(a){a.preventDefault();
a.stopPropagation();if("SPAN"==a.target.nodeName)a.target.parentNode.remove(),search("");else if("BUTTON"==a.target.nodeName){var b=decodeURI(a.target.getAttribute("data-filter"));domCache.searchstr.value=b.substring(b.indexOf("'")+1,b.length-1);var c=b.substring(0,b.indexOf(" "));selectTag("searchtags","searchtagsdesc",c);b=b.substring(b.indexOf(" ")+1);b=b.substring(0,b.indexOf(" "));document.getElementById("searchMatch").value=b;a.target.remove();search(domCache.searchstr.value)}},!1);document.getElementById("searchMatch").addEventListener("change",
function(a){search(domCache.searchstr.value)},!1);document.getElementById("SearchList").getElementsByTagName("tr")[0].addEventListener("click",function(a){if(settings.featAdvsearch&&"TH"==a.target.nodeName){var b=a.target.getAttribute("data-col");if("Duration"!=b){var c=app.current.sort,d=!0;if(c==b||c=="-"+b)0==c.indexOf("-")?(d=!0,c.substring(1)):d=!1;0==d?(c="-"+b,d=!0):(d=!1,c=b);for(var h=document.getElementById("SearchList").getElementsByClassName("sort-dir"),l=0;l<h.length;l++)h[l].remove();
app.current.sort=c;a.target.innerHTML=b+'<span class="sort-dir material-icons pull-right">'+(1==d?"arrow_drop_up":"arrow_drop_down")+"</span>";appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.sort+"/"+app.current.search)}}},!1);document.getElementById("BrowseDatabaseByTagDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,a.target.getAttribute("data-tag"),"0/"+app.current.filter+
"/"+app.current.sort+"/"+app.current.search)},!1);document.getElementsByTagName("body")[0].addEventListener("click",function(a){hideMenu()},!1);dragAndDropTable("QueueCurrentList");dragAndDropTable("BrowsePlaylistsDetailList");dragAndDropTableHeader("QueueCurrent");dragAndDropTableHeader("QueueLastPlayed");dragAndDropTableHeader("Search");dragAndDropTableHeader("BrowseFilesystem");dragAndDropTableHeader("BrowsePlaylistsDetail");window.addEventListener("hashchange",appRoute,!1);window.addEventListener("focus",
function(){sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName&&"SELECT"!=a.target.tagName&&!a.ctrlKey&&!a.altKey){var b=keymap[a.key];b&&"function"===typeof window[b.cmd]&&(void 0==keymap[a.key].req||1==settings[keymap[a.key].req])&&parseCmd(a,b)}},!1);"serviceWorker"in navigator&&"https"==document.URL.substring(0,5)&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.min.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",
a.scope);a.update()},function(a){console.log("ServiceWorker registration failed: ",a)})});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a;domCache.btnAdd.classList.remove("hide")});domCache.btnAdd.addEventListener("click",function(a){domCache.btnAdd.classList.add("hide");deferredPrompt.prompt();deferredPrompt.userChoice.then(function(a){"accepted"===a.outcome?
console.log("User accepted the A2HS prompt"):console.log("User dismissed the A2HS prompt");deferredPrompt=null})});window.addEventListener("appinstalled",function(a){console.log("myMPD installed as app")});window.addEventListener("beforeunload",function(){socket.onclose=function(){};socket.close();websocketConnected=!1})}
function parseCmd(a,b){a.preventDefault();a=b;"string"==typeof b&&(a=JSON.parse(b));if("function"===typeof window[a.cmd])switch(a.cmd){case "sendAPI":sendAPI.apply(null,$jscomp.arrayFromIterable(a.options));break;default:window[a.cmd].apply(null,$jscomp.arrayFromIterable(a.options))}}
function search(a){if(settings.featAdvsearch){for(var b="(",c=domCache.searchCrumb.children,d=0;d<c.length;d++)b+="("+decodeURI(c[d].getAttribute("data-filter"))+")",""!=a&&(b+=" AND ");""!=a?(c=document.getElementById("searchMatch"),b+="("+app.current.filter+" "+c.options[c.selectedIndex].value+" '"+a+"'))"):b+=")";2>=b.length&&(b="");appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+encodeURI(b))}else appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+a)}
function dragAndDropTable(a){var b=document.getElementById(a).getElementsByTagName("tbody")[0];b.addEventListener("dragstart",function(a){"TR"==a.target.nodeName&&(a.target.classList.add("opacity05"),a.dataTransfer.setDragImage(a.target,0,0),a.dataTransfer.effectAllowed="move",a.dataTransfer.setData("Text",a.target.getAttribute("id")),dragEl=a.target.cloneNode(!0))},!1);b.addEventListener("dragleave",function(a){a.preventDefault();if("TR"==dragEl.nodeName){var b=a.target;"TD"==a.target.nodeName&&
(b=a.target.parentNode);"TR"==b.nodeName&&b.classList.remove("dragover")}},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");c=a.target;"TD"==a.target.nodeName&&(c=a.target.parentNode);"TR"==c.nodeName&&c.classList.add("dragover");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=
function search(a){if(settings.featAdvsearch){for(var b="(",c=domCache.searchCrumb.children,d=0;d<c.length;d++)b+="("+decodeURI(c[d].getAttribute("data-filter"))+")",""!=a&&(b+=" AND ");""!=a?(c=document.getElementById("searchMatch"),b+="("+app.current.filter+" "+c.options[c.selectedIndex].value+" '"+a+"'))"):b+=")";2>=b.length&&(b="");appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+app.current.sort+"/"+encodeURI(b))}else appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+app.current.sort+
"/"+a)}
function dragAndDropTable(a){var b=document.getElementById(a).getElementsByTagName("tbody")[0];b.addEventListener("dragstart",function(a){"TR"==a.target.nodeName&&(a.target.classList.add("opacity05"),a.dataTransfer.setDragImage(a.target,0,0),a.dataTransfer.effectAllowed="move",a.dataTransfer.setData("Text",a.target.getAttribute("id")),dragEl=a.target.cloneNode(!0))},!1);b.addEventListener("dragleave",function(a){a.preventDefault();if("TR"==dragEl.nodeName){var b=a.target;"TD"==a.target.nodeName&&(b=
a.target.parentNode);"TR"==b.nodeName&&b.classList.remove("dragover")}},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");c=a.target;"TD"==a.target.nodeName&&(c=a.target.parentNode);"TR"==c.nodeName&&c.classList.add("dragover");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=
b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");document.getElementById(a.dataTransfer.getData("Text"))&&document.getElementById(a.dataTransfer.getData("Text")).classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TR"==dragEl.nodeName){var d=c.target;"TD"==c.target.nodeName&&(d=c.target.parentNode);var e=document.getElementById(c.dataTransfer.getData("Text")).getAttribute("data-songpos"),
f=d.getAttribute("data-songpos");document.getElementById(c.dataTransfer.getData("Text")).remove();dragEl.classList.remove("opacity05");b.insertBefore(dragEl,d);c=b.getElementsByClassName("dragover");d=c.length;for(var g=0;g<d;g++)c[g].classList.remove("dragover");document.getElementById(a).classList.add("opacity05");"Queue"==app.current.app&&"Current"==app.current.tab?sendAPI({cmd:"MPD_API_QUEUE_MOVE_TRACK",data:{from:e,to:f}}):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&
playlistMoveTrack(e,f)}},!1)}
@ -68,39 +73,40 @@ function dragAndDropTableHeader(a){if(document.getElementById(a+"List"))var b=do
function(a){a.preventDefault();"TH"==dragEl.nodeName&&"TH"==a.target.nodeName&&a.target.classList.remove("dragover-th")},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TH"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover-th"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover-th");"TH"==a.target.nodeName&&a.target.classList.add("dragover-th");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TH"==dragEl.nodeName){for(var c=
b.getElementsByClassName("dragover-th"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover-th");this.querySelector("[data-col="+a.dataTransfer.getData("Text")+"]")&&this.querySelector("[data-col="+a.dataTransfer.getData("Text")+"]").classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TH"==dragEl.nodeName){this.querySelector("[data-col="+c.dataTransfer.getData("Text")+"]").remove();dragEl.classList.remove("opacity05");b.insertBefore(dragEl,
c.target);c=b.getElementsByClassName("dragover-th");for(var d=c.length,e=0;e<d;e++)c[e].classList.remove("dragover-th");document.getElementById(a+"List")?(document.getElementById(a+"List").classList.add("opacity05"),saveCols(a)):saveCols(a,this.parentNode.parentNode)}},!1)}function playlistMoveTrack(a,b){sendAPI({cmd:"MPD_API_PLAYLIST_MOVE_TRACK",data:{plist:app.current.search,from:a,to:b}})}
function webSocketConnect(){var a=getWsUrl();socket=new WebSocket(a);try{socket.onopen=function(){console.log("connected");showNotification("Connected to myMPD: "+a,"","","success");modalConnectionError.hide();appRoute();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},socket.onmessage=function(b){if(b.data!==lastState&&0!=b.data.length){try{var c=JSON.parse(b.data)}catch(d){console.log("Invalid JSON data received: "+b.data)}switch(c.type){case "update_state":parseState(c);break;case "disconnected":showNotification("Lost connection to myMPD: "+
function webSocketConnect(){var a=getWsUrl();socket=new WebSocket(a);try{socket.onopen=function(){console.log("Websocket is connected")},socket.onmessage=function(b){try{var c=JSON.parse(b.data)}catch(d){console.log("Invalid JSON data received: "+b.data)}switch(c.type){case "welcome":websocketConnected=!0;showNotification("Connected to myMPD: "+a,"","","success");modalConnectionError.hide();appRoute();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);break;case "update_state":parseState(c);break;case "disconnected":showNotification("Lost connection to myMPD: "+
a,"","","danger");break;case "update_queue":"Queue"===app.current.app&&getQueue();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);break;case "update_options":getSettings();break;case "update_outputs":sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs);break;case "update_started":updateDBstarted(!1);break;case "update_database":case "update_finished":updateDBfinished(c.type);break;case "update_volume":parseVolume(c);break;case "update_stored_playlist":"Browse"==app.current.app&&"Playlists"==
app.current.tab&&"All"==app.current.view?sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists);break;case "error":showNotification(c.data,"","","danger")}}},socket.onclose=function(){console.log("disconnected");modalConnectionError.show();
setTimeout(function(){console.log("reconnect");webSocketConnect()},3E3)}}catch(b){alert("Error: "+b)}}function getWsUrl(){var a=window.location.hostname,b=window.location.protocol,c=window.location.port;a=("https:"==b?"wss://":"ws://")+a+(""!=c?":"+c:"")+"/ws";return document.getElementById("wsUrl").innerText=a}
app.current.tab&&"All"==app.current.view?sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists);break;case "error":showNotification(c.data,"","","danger")}},socket.onclose=function(){console.log("Websocket is disconnected");
1==appInited&&modalConnectionError.show();websocketConnected=!1;null!=websocketTimer&&clearTimeout(websocketTimer);websocketTimer=setTimeout(function(){console.log("Reconnecting websocket");webSocketConnect()},3E3)}}catch(b){alert("Error: "+b)}}function getWsUrl(){var a=window.location.hostname,b=window.location.protocol,c=window.location.port;a=("https:"==b?"wss://":"ws://")+a+(""!=c?":"+c:"")+"/ws";return document.getElementById("wsUrl").innerText=a}
function parseStats(a){document.getElementById("mpdstats_artists").innerText=a.data.artists;document.getElementById("mpdstats_albums").innerText=a.data.albums;document.getElementById("mpdstats_songs").innerText=a.data.songs;document.getElementById("mpdstats_dbPlaytime").innerText=beautifyDuration(a.data.dbPlaytime);document.getElementById("mpdstats_playtime").innerText=beautifyDuration(a.data.playtime);document.getElementById("mpdstats_uptime").innerText=beautifyDuration(a.data.uptime);var b=new Date(1E3*
a.data.dbUpdated);document.getElementById("mpdstats_dbUpdated").innerText=b.toUTCString();document.getElementById("mympdVersion").innerText=a.data.mympdVersion;document.getElementById("mpdVersion").innerText=a.data.mpdVersion;document.getElementById("libmpdclientVersion").innerText=a.data.libmpdclientVersion}function toggleBtn(a,b){if(a=document.getElementById(a))void 0==b&&(b=a.classList.contains("active")?0:1),1==b||1==b?a.classList.add("active"):a.classList.remove("active")}
function filterCols(a){var b=settings.tags.slice();0==settings.featTags&&b.push("Title");b.push("Duration");"colsQueueCurrent"==a||"colsBrowsePlaylistsDetail"==a||"colsQueueLastPlayed"==a?b.push("Pos"):"colsBrowseFilesystem"==a&&b.push("Type");"colsQueueLastPlayed"==a&&b.push("LastPlayed");for(var c=[],d=0;d<settings[a].length;d++)b.includes(settings[a][d])&&c.push(settings[a][d]);settings[a]=c}
function parseSettings(a){settings=a.data;toggleBtn("btnRandom",settings.random);toggleBtn("btnConsume",settings.consume);toggleBtn("btnSingle",settings.single);toggleBtn("btnRepeat",settings.repeat);void 0!=settings.crossfade?(document.getElementById("inputCrossfade").removeAttribute("disabled"),document.getElementById("inputCrossfade").value=settings.crossfade):document.getElementById("inputCrossfade").setAttribute("disabled","disabled");void 0!=settings.mixrampdb?(document.getElementById("inputMixrampdb").removeAttribute("disabled"),
document.getElementById("inputMixrampdb").value=settings.mixrampdb):document.getElementById("inputMixrampdb").setAttribute("disabled","disabled");void 0!=settings.mixrampdelay?(document.getElementById("inputMixrampdelay").removeAttribute("disabled"),document.getElementById("inputMixrampdelay").value=settings.mixrampdelay):document.getElementById("inputMixrampdelay").setAttribute("disabled","disabled");document.getElementById("selectReplaygain").value=settings.replaygain;a=document.getElementById("btnnotifyWeb");
function parseSettings(){toggleBtn("btnRandom",settings.random);toggleBtn("btnConsume",settings.consume);toggleBtn("btnSingle",settings.single);toggleBtn("btnRepeat",settings.repeat);toggleBtn("btnAutoPlay",settings.autoPlay);void 0!=settings.crossfade?(document.getElementById("inputCrossfade").removeAttribute("disabled"),document.getElementById("inputCrossfade").value=settings.crossfade):document.getElementById("inputCrossfade").setAttribute("disabled","disabled");void 0!=settings.mixrampdb?(document.getElementById("inputMixrampdb").removeAttribute("disabled"),
document.getElementById("inputMixrampdb").value=settings.mixrampdb):document.getElementById("inputMixrampdb").setAttribute("disabled","disabled");void 0!=settings.mixrampdelay?(document.getElementById("inputMixrampdelay").removeAttribute("disabled"),document.getElementById("inputMixrampdelay").value=settings.mixrampdelay):document.getElementById("inputMixrampdelay").setAttribute("disabled","disabled");document.getElementById("selectReplaygain").value=settings.replaygain;var a=document.getElementById("btnnotifyWeb");
notificationsSupported()?settings.notificationWeb?(toggleBtn("btnnotifyWeb",settings.notificationWeb),Notification.requestPermission(function(a){"permission"in Notification||(Notification.permission=a);"granted"===a?toggleBtn("btnnotifyWeb",1):(toggleBtn("btnnotifyWeb",0),settings.notificationWeb=!0)})):toggleBtn("btnnotifyWeb",0):(a.setAttribute("disabled","disabled"),toggleBtn("btnnotifyWeb",0));toggleBtn("btnnotifyPage",settings.notificationPage);var b="featStickers featSmartpls featPlaylists featTags featLocalplayer featSyscmds featCoverimage featAdvsearch".split(" ");
document.documentElement.style.setProperty("--mympd-coverimagesize",settings.coverimagesize+"px");for(var c=0;c<b.length;c++){var d=document.getElementsByClassName(b[c]),e=d.length,f=1==settings[b[c]]?"":"none";for(a=0;a<e;a++)d[a].style.display=f}if(0==settings.featTags)app.apps.Browse.active="Filesystem",app.apps.Search.state="0/filename/",app.apps.Queue.state="0/filename/",settings.colsQueueCurrent=["Pos","Title","Duration"],settings.colsQueueLastPlayed=["Pos","Title","LastPlayed"],settings.colsSearch=
["Title","Duration"],settings.colsBrowseFilesystem=["Type","Title","Duration"],settings.colsBrowseDatabase=["Track","Title","Duration"],settings.colsPlayback=[];else{b="";for(a=0;a<settings.colsPlayback.length;a++)b+='<div id="current'+settings.colsPlayback[a]+'" data-tag="'+settings.colsPlayback[a]+'" data-name="'+encodeURI(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+'"><small>'+settings.colsPlayback[a]+"</small><h4",settings.browsetags.includes(settings.colsPlayback[a])&&(b+=
' class="clickable"'),b+=">"+(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+"</h4></div>";document.getElementById("cardPlaybackTags").innerHTML=b}1==settings.mixramp?document.getElementsByClassName("mixramp")[0].style.display="":document.getElementsByClassName("mixramp")[0].style.display="none";!settings.tags.includes("AlbumArtist")&&settings.featTags&&(settings.tags.includes("Artist")?app.apps.Browse.tabs.Database.active="Artist":app.apps.Browse.tabs.Database.active=settings.tags[0]);
document.getElementById("selectJukeboxMode").value=settings.jukeboxMode;document.getElementById("inputJukeboxQueueLength").value=settings.jukeboxQueueLength;0==settings.jukeboxMode||2==settings.jukeboxMode?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled","disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled","disabled")):1==settings.jukeboxMode&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"));
settings.featPlaylists?(playlistEl="selectJukeboxPlaylist",sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:0,filter:"-"}},getAllPlaylists)):document.getElementById("selectJukeboxPlaylist").innerHTML="<option>Database</option>";settings.tags.sort();settings.searchtags.sort();settings.browsetags.sort();filterCols("colsSearch");filterCols("colsQueueCurrent");filterCols("colsQueueLastPlayed");filterCols("colsBrowsePlaylistsDetail");filterCols("colsBrowseFilesystem");filterCols("colsBrowseDatabase");
filterCols("colsPlayback");settings.featLocalplayer&&(""==settings.streamurl?(settings.mpdstream="http://",settings.mpdstream="127.0.0.1"==settings.mpdhost||"localhost"==settings.mpdhost?settings.mpdstream+window.location.hostname:settings.mpdstream+settings.mpdhost,settings.mpdstream+=":"+settings.streamport+"/"):settings.mpdstream=settings.streamurl);addTagList("BrowseDatabaseByTagDropdown","browsetags");addTagList("searchqueuetags","searchtags");addTagList("searchtags","searchtags");for(a=0;a<
settings.tags.length;a++)app.apps.Browse.tabs.Database.views[settings.tags[a]]={state:"0/-/",scrollPos:0};if(settings.featSyscmds){document.getElementById("mainMenuDropdown");b="";c=settings.syscmds.length;if(0<c)for(b='<div class="dropdown-divider"></div>',a=0;a<c;a++)b+='<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["'+settings.syscmds[a]+"\"]}'>"+settings.syscmds[a]+"</a>";document.getElementById("syscmds").innerHTML=b}else document.getElementById("syscmds").innerHTML=
"";dropdownMainMenu=new Dropdown(document.getElementById("mainMenu"));setCols("QueueCurrent");setCols("Search");setCols("QueueLastPlayed");setCols("BrowseFilesystem");setCols("BrowsePlaylistsDetail");setCols("BrowseDatabase",".tblAlbumTitles");setCols("Playback");"Queue"==app.current.app&&"Current"==app.current.tab?getQueue():"Queue"==app.current.app&&"LastPlayed"==app.current.tab?appRoute():"Search"==app.current.app?appRoute():"Browse"==app.current.app&&"Filesystem"==app.current.tab?appRoute():"Browse"==
app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?appRoute():"Browse"==app.current.app&&"Database"==app.current.tab&&""!=app.current.search&&appRoute()}
document.documentElement.style.setProperty("--mympd-coverimagesize",settings.coverimagesize+"px");document.documentElement.style.setProperty("--mympd-backgroundcolor",settings.backgroundcolor);for(var c=0;c<b.length;c++){var d=document.getElementsByClassName(b[c]),e=d.length,f=1==settings[b[c]]?"":"none";for(a=0;a<e;a++)d[a].style.display=f}if(0==settings.featTags)app.apps.Browse.active="Filesystem",app.apps.Search.state="0/filename/",app.apps.Queue.state="0/filename/",settings.colsQueueCurrent=["Pos",
"Title","Duration"],settings.colsQueueLastPlayed=["Pos","Title","LastPlayed"],settings.colsSearch=["Title","Duration"],settings.colsBrowseFilesystem=["Type","Title","Duration"],settings.colsBrowseDatabase=["Track","Title","Duration"],settings.colsPlayback=[];else{b="";for(a=0;a<settings.colsPlayback.length;a++)b+='<div id="current'+settings.colsPlayback[a]+'" data-tag="'+settings.colsPlayback[a]+'" data-name="'+encodeURI(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+'"><small>'+
settings.colsPlayback[a]+"</small><h4",settings.browsetags.includes(settings.colsPlayback[a])&&(b+=' class="clickable"'),b+=">"+(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+"</h4></div>";document.getElementById("cardPlaybackTags").innerHTML=b}1==settings.mixramp?document.getElementsByClassName("mixramp")[0].style.display="":document.getElementsByClassName("mixramp")[0].style.display="none";!settings.tags.includes("AlbumArtist")&&settings.featTags&&(settings.tags.includes("Artist")?
app.apps.Browse.tabs.Database.active="Artist":app.apps.Browse.tabs.Database.active=settings.tags[0]);settings.tags.includes("Title")&&(app.apps.Search.state="0/any/Title/");document.getElementById("selectJukeboxMode").value=settings.jukeboxMode;document.getElementById("inputJukeboxQueueLength").value=settings.jukeboxQueueLength;0==settings.jukeboxMode||2==settings.jukeboxMode?(document.getElementById("inputJukeboxQueueLength").setAttribute("disabled","disabled"),document.getElementById("selectJukeboxPlaylist").setAttribute("disabled",
"disabled")):1==settings.jukeboxMode&&(document.getElementById("inputJukeboxQueueLength").removeAttribute("disabled"),document.getElementById("selectJukeboxPlaylist").removeAttribute("disabled"));settings.featPlaylists?(playlistEl="selectJukeboxPlaylist",sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:0,filter:"-"}},getAllPlaylists)):document.getElementById("selectJukeboxPlaylist").innerHTML="<option>Database</option>";settings.tags.sort();settings.searchtags.sort();settings.browsetags.sort();filterCols("colsSearch");
filterCols("colsQueueCurrent");filterCols("colsQueueLastPlayed");filterCols("colsBrowsePlaylistsDetail");filterCols("colsBrowseFilesystem");filterCols("colsBrowseDatabase");filterCols("colsPlayback");settings.featLocalplayer&&(""==settings.streamurl?(settings.mpdstream="http://",settings.mpdstream="127.0.0.1"==settings.mpdhost||"localhost"==settings.mpdhost?settings.mpdstream+window.location.hostname:settings.mpdstream+settings.mpdhost,settings.mpdstream+=":"+settings.streamport+"/"):settings.mpdstream=
settings.streamurl);addTagList("BrowseDatabaseByTagDropdown","browsetags");addTagList("searchqueuetags","searchtags");addTagList("searchtags","searchtags");for(a=0;a<settings.tags.length;a++)app.apps.Browse.tabs.Database.views[settings.tags[a]]={state:"0/-/-/",scrollPos:0};if(settings.featSyscmds){document.getElementById("mainMenuDropdown");b="";c=settings.syscmdList.length;if(0<c)for(b='<div class="dropdown-divider"></div>',a=0;a<c;a++)b+='<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["'+
settings.syscmdList[a]+"\"]}'>"+settings.syscmdList[a]+"</a>";document.getElementById("syscmds").innerHTML=b}else document.getElementById("syscmds").innerHTML="";dropdownMainMenu=new Dropdown(document.getElementById("mainMenu"));setCols("QueueCurrent");setCols("Search");setCols("QueueLastPlayed");setCols("BrowseFilesystem");setCols("BrowsePlaylistsDetail");setCols("BrowseDatabase",".tblAlbumTitles");setCols("Playback");"Queue"==app.current.app&&"Current"==app.current.tab?getQueue():"Queue"==app.current.app&&
"LastPlayed"==app.current.tab?appRoute():"Search"==app.current.app?appRoute():"Browse"==app.current.app&&"Filesystem"==app.current.tab?appRoute():"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?appRoute():"Browse"==app.current.app&&"Database"==app.current.tab&&""!=app.current.search&&appRoute();settingsParsed=!0}
function setCols(a,b){var c="",d=settings.tags.slice();0==settings.featTags&&d.push("Title");d.push("Duration");"QueueCurrent"!=a&&"BrowsePlaylistsDetail"!=a&&"QueueLastPlayed"!=a||d.push("Pos");"BrowseFilesystem"==a&&d.push("Type");"QueueLastPlayed"==a&&d.push("LastPlayed");d.sort();for(var e=0;e<d.length;e++)if("Playback"!=a||"Title"!=d[e])c+='<div class="form-check"><input class="form-check-input" type="checkbox" value="1" name="'+d[e]+'"',settings["cols"+a].includes(d[e])&&(c+="checked"),c+='><label class="form-check-label text-light" for="'+
d[e]+'">&nbsp;&nbsp;'+d[e]+"</label></div>";document.getElementById(a+"ColsDropdown").firstChild.innerHTML=c;d=document.getElementById("SearchList").getAttribute("data-sort");""==d&&(d=settings.featTags?"Title":"Filename");if("Playback"!=a){c="";for(e=0;e<settings["cols"+a].length;e++){var f=settings["cols"+a][e];c+='<th draggable="true" data-col="'+f+'">';if("Track"==f||"Pos"==f)f="#";c+=f;"Search"==a&&f==d&&(f=!1,0==d.indexOf("-")&&(f=!0,d=d.substring(1)),c+='<span class="sort-dir material-icons pull-right">'+
(1==f?"arrow_drop_up":"arrow_drop_down")+"</span>");c+="</th>"}c+="<th></th>";if(void 0==b)document.getElementById(a+"List").getElementsByTagName("tr")[0].innerHTML=c;else for(a=document.querySelectorAll(b),e=0;e<a.length;e++)a[e].getElementsByTagName("tr")[0].innerHTML=c}}function getSettings(){sendAPI({cmd:"MPD_API_SETTINGS_GET"},parseSettings)}
d[e]+'">&nbsp;&nbsp;'+d[e]+"</label></div>";document.getElementById(a+"ColsDropdown").firstChild.innerHTML=c;d=app.current.sort;"Search"==a&&"0/any/Title/"==app.apps.Search.state&&(d=settings.tags.includes("Title")?"Title":0==settings.featTags?"Filename":"-");if("Playback"!=a){c="";for(e=0;e<settings["cols"+a].length;e++){var f=settings["cols"+a][e];c+='<th draggable="true" data-col="'+f+'">';if("Track"==f||"Pos"==f)f="#";c+=f;"Search"!=a||f!=d&&"-"+f!=d||(f=!1,0==app.current.sort.indexOf("-")&&(f=
!0),c+='<span class="sort-dir material-icons pull-right">'+(1==f?"arrow_drop_up":"arrow_drop_down")+"</span>");c+="</th>"}c+="<th></th>";if(void 0==b)document.getElementById(a+"List").getElementsByTagName("tr")[0].innerHTML=c;else for(a=document.querySelectorAll(b),e=0;e<a.length;e++)a[e].getElementsByTagName("tr")[0].innerHTML=c}}function getSettings(){0==settingsLock&&(settingsLock=!0,sendAPI({cmd:"MYMPD_API_SETTINGS_GET"},getMpdSettings))}
function getMpdSettings(a){settingsNew=a.data;sendAPI({cmd:"MPD_API_SETTINGS_GET"},joinSettings)}function joinSettings(a){for(var b in a.data)settingsNew[b]=a.data[b];settings=Object.assign({},settingsNew);settingsLock=!1;parseSettings()}
function saveCols(a,b){var c=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input");var d=void 0==b?document.getElementById(a+"List").getElementsByTagName("tr")[0]:"string"==typeof b?document.querySelector(b).getElementsByTagName("tr")[0]:b.getElementsByTagName("tr")[0];for(b=0;b<c.length;b++){var e=d.querySelector("[data-col="+c[b].name+"]");0==c[b].checked?e&&e.remove():e||(e=document.createElement("th"),e.innerText=c[b].name,e.setAttribute("data-col",c[b].name),d.appendChild(e))}a=
{cmd:"MPD_API_COLS_SAVE",data:{table:"cols"+a,cols:[]}};c=d.getElementsByTagName("th");for(b=0;b<c.length;b++)(d=c[b].getAttribute("data-col"))&&a.data.cols.push(d);sendAPI(a,getSettings)}
function saveColsPlayback(a){for(var b=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input"),c=document.getElementById("cardPlaybackTags"),d=0;d<b.length;d++){var e=document.getElementById("current"+b[d].name);0==b[d].checked?e&&e.remove():e||(e=document.createElement("div"),e.innerHTML="<small>"+b[d].name+"</small><h4></h4>",e.setAttribute("id","current"+b[d].name),e.setAttribute("data-tag",b[d].name),c.appendChild(e))}a={cmd:"MPD_API_COLS_SAVE",data:{table:"cols"+a,
cols:[]}};c=c.getElementsByTagName("div");for(d=0;d<c.length;d++)(b=c[d].getAttribute("data-tag"))&&a.data.cols.push(b);sendAPI(a,getSettings)}
{cmd:"MYMPD_API_COLS_SAVE",data:{table:"cols"+a,cols:[]}};c=d.getElementsByTagName("th");for(b=0;b<c.length;b++)(d=c[b].getAttribute("data-col"))&&a.data.cols.push(d);sendAPI(a,getSettings)}
function saveColsPlayback(a){for(var b=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input"),c=document.getElementById("cardPlaybackTags"),d=0;d<b.length;d++){var e=document.getElementById("current"+b[d].name);0==b[d].checked?e&&e.remove():e||(e=document.createElement("div"),e.innerHTML="<small>"+b[d].name+"</small><h4></h4>",e.setAttribute("id","current"+b[d].name),e.setAttribute("data-tag",b[d].name),c.appendChild(e))}a={cmd:"MYMPD_API_COLS_SAVE",data:{table:"cols"+
a,cols:[]}};c=c.getElementsByTagName("div");for(d=0;d<c.length;d++)(b=c[d].getAttribute("data-tag"))&&a.data.cols.push(b);sendAPI(a,getSettings)}
function parseOutputs(a){for(var b="",c=a.data.outputs.length,d=0;d<c;d++)b+='<button id="btnOutput'+a.data.outputs[d].id+'" data-output-id="'+a.data.outputs[d].id+'" class="btn btn-secondary btn-block',1==a.data.outputs[d].state&&(b+=" active"),b+='"><span class="material-icons float-left">volume_up</span> '+a.data.outputs[d].name+"</button>";domCache.outputs.innerHTML=b}
function setCounter(a,b,c){currentSong.totalTime=b;currentSong.elapsedTime=c;currentSong.currentSongId=a;var d=Math.floor(b/60),e=b-60*d,f=Math.floor(c/60),g=c-60*f;domCache.progressBar.value=Math.floor(1E3*c/b);b=f+":"+(10>g?"0":"")+g+" / "+d+":"+(10>e?"0":"")+e;domCache.counter.innerText=b;if(lastState&&lastState.data.currentSongId!=a&&(c=document.getElementById("queueTrackId"+lastState.data.currentSongId))){if(d=c.querySelector("[data-col=Duration]"))d.innerText=c.getAttribute("data-duration");
if(d=c.querySelector("[data-col=Pos]"))d.classList.remove("material-icons"),d.innerText=c.getAttribute("data-songpos");c.classList.remove("font-weight-bold")}if(c=document.getElementById("queueTrackId"+a)){if(d=c.querySelector("[data-col=Duration]"))d.innerText=b;(d=c.querySelector("[data-col=Pos]"))&&!d.classList.contains("material-icons")&&(d.classList.add("material-icons"),d.innerText="play_arrow");c.classList.add("font-weight-bold")}progressTimer&&clearTimeout(progressTimer);"play"==playstate&&
(progressTimer=setTimeout(function(){currentSong.elapsedTime++;setCounter(currentSong.currentSongId,currentSong.totalTime,currentSong.elapsedTime)},1E3))}
function parseState(a){if(JSON.stringify(a)!==JSON.stringify(lastState)){if(1==a.data.state){for(var b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="stop"}else if(2==a.data.state){for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="pause";playstate="play"}else{for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="pause"}-1==a.data.nextSongPos&&0==settings.jukeboxMode?domCache.btnNext.setAttribute("disabled","disabled"):
domCache.btnNext.removeAttribute("disabled");0>=a.data.songPos?domCache.btnPrev.setAttribute("disabled","disabled"):domCache.btnPrev.removeAttribute("disabled");if(0==a.data.queueLength)for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].setAttribute("disabled","disabled");else for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].removeAttribute("disabled");domCache.badgeQueueItems.innerText=a.data.queueLength;parseVolume(a);setCounter(a.data.currentSongId,a.data.totalTime,a.data.elapsedTime);
lastState&&lastState.data.currentSongId!=a.data.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);if("-1"==a.data.songPos){domCache.currentTitle.innerText="Not playing";domCache.currentCover.style.backgroundImage="";var c=document.getElementById("cardPlaybackTags").getElementsByTagName("h4");for(b=0;b<c.length;b++)c[b].innerText=""}"Queue"==app.current.app&&"LastPlayed"==app.current.tab&&sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);
lastState&&lastState.data.currentSongId==a.data.currentSongId||sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);if("-1"==a.data.songPos){domCache.currentTitle.innerText="Not playing";domCache.currentCover.style.backgroundImage="";var c=document.getElementById("cardPlaybackTags").getElementsByTagName("h4");for(b=0;b<c.length;b++)c[b].innerText=""}"Queue"==app.current.app&&"LastPlayed"==app.current.tab&&sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);
lastState=a}}function parseVolume(a){-1==a.data.volume?(domCache.volumePrct.innerText="Volumecontrol disabled",domCache.volumeControl.classList.add("hide")):(domCache.volumeControl.classList.remove("hide"),domCache.volumePrct.innerText=a.data.volume+" %",domCache.volumeMenu.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume}
function getQueue(){2<=app.current.search.length?sendAPI({cmd:"MPD_API_QUEUE_SEARCH",data:{filter:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue):sendAPI({cmd:"MPD_API_QUEUE_LIST",data:{offset:app.current.page}},parseQueue)}
function parseQueue(a){0<a.totalTime&&a.totalEntities<=settings.maxElementsPerPage?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song")+" \u2013 "+beautifyDuration(a.totalTime):0<a.totalEntities?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song"):document.getElementById("cardFooterQueue").innerText="";var b=a.data.length,c=document.getElementById("QueueCurrentList");c.setAttribute("data-version",
@ -140,11 +146,11 @@ function setPagination(a,b){var c=app.current.app+(void 0==app.current.tab?"":ap
-1==a&&b>=settings.maxElementsPerPage?(document.getElementById(c+e[f]+"Next").removeAttribute("disabled"),document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):(document.getElementById(c+e[f]+"Next").setAttribute("disabled","disabled"),document.getElementById(c+e[f]).classList.add("hide"),document.getElementById(c+"ButtonsBottom").classList.add("hide"));0<app.current.page?(document.getElementById(c+e[f]+"Prev").removeAttribute("disabled"),
document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):document.getElementById(c+e[f]+"Prev").setAttribute("disabled","disabled")}}function appendQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:b}});showNotification('"'+c+'" added',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_ADD_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" added',"","","success")}}
function appendAfterQueue(a,b,c,d){switch(a){case "song":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK_AFTER",data:{uri:b,to:c}}),showNotification('"'+d+'" added to pos '+c,"","","success")}}function replaceQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_TRACK",data:{uri:b}});showNotification('"'+c+'" replaced',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" replaced',"","","success")}}
function clickTitle(){var a=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=a&&songDetails(a)}function gotoBrowse(a){var b=a.parentNode.getAttribute("data-tag");a=decodeURI(a.parentNode.getAttribute("data-name"));""!=b&&""!=a&&"-"!=a&&settings.browsetags.includes(b)&&appGoto("Browse","Database",b,"0/-/"+a)}function songDetails(a){sendAPI({cmd:"MPD_API_DATABASE_SONGDETAILS",data:{uri:a}},parseSongDetails);modalSongDetails.show()}
function clickTitle(){var a=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=a&&songDetails(a)}function gotoBrowse(a){var b=a.parentNode.getAttribute("data-tag");a=decodeURI(a.parentNode.getAttribute("data-name"));""!=b&&""!=a&&"-"!=a&&settings.browsetags.includes(b)&&appGoto("Browse","Database",b,"0/-/-/"+a)}function songDetails(a){sendAPI({cmd:"MPD_API_DATABASE_SONGDETAILS",data:{uri:a}},parseSongDetails);modalSongDetails.show()}
function parseSongDetails(a){var b=document.getElementById("modalSongDetails");b.getElementsByClassName("album-cover")[0].style.backgroundImage='url("'+a.data.cover+'")';b.getElementsByTagName("h1")[0].innerText=a.data.Title;for(var c="",d=0;d<settings.tags.length;d++)c+="<tr><th>"+settings.tags[d]+'</th><td data-tag="'+settings.tags[d]+'" data-name="'+encodeURI(a.data[settings.tags[d]])+'">',c=settings.browsetags.includes(settings.tags[d])?c+('<a class="text-success" href="#">'+a.data[settings.tags[d]]+
"</a>"):c+a.data[settings.tags[d]],c+="</td></tr>";var e=a.data.Duration;d=Math.floor(e/60);e-=60*d;c+="<tr><th>Duration</th><td>"+(d+":"+(10>e?"0":"")+e)+"</td></tr>";c=settings.featLibrary?c+('<tr><th>Filename</th><td><a class="text-success" href="/library/'+encodeURI(a.data.uri)+'">'+a.data.uri+"</a></td></tr>"):c+("<tr><th>Filename</th><td>"+a.data.uri+"</td></tr>");1==settings.featStickers&&(c+='<tr><th colspan="2">Statistics</th></tr><tr><th>Play count</th><td>'+a.data.playCount+"</td></tr><tr><th>Skip count</th><td>"+
a.data.skipCount+"</td></tr><tr><th>Last played</th><td>"+(0==a.data.lastPlayed?"never":(new Date(1E3*a.data.lastPlayed)).toUTCString())+'</td></tr><tr><th>Like</th><td><div class="btn-group btn-group-sm"><button title="Dislike song" id="btnVoteDown2" data-href=\'{"cmd": "voteSong", "options": [0]}\' class="btn btn-sm btn-light material-icons">thumb_down</button><button title="Like song" id="btnVoteUp2" data-href=\'{"cmd": "voteSong", "options": [2]}\' class="btn btn-sm btn-light material-icons">thumb_up</button></div></td></tr>');
b.getElementsByTagName("tbody")[0].innerHTML=c;setVoteSongBtns(a.data.like,a.data.uri)}function execSyscmd(a){sendAPI({cmd:"MPD_API_SYSCMD",data:{cmd:a}})}function playlistDetails(a){document.getElementById("BrowsePlaylistsAllList").classList.add("opacity05");appGoto("Browse","Playlists","Detail","0/-/"+a)}function removeFromPlaylist(a,b){b--;sendAPI({cmd:"MPD_API_PLAYLIST_RM_TRACK",data:{uri:a,track:b}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")}
b.getElementsByTagName("tbody")[0].innerHTML=c;setVoteSongBtns(a.data.like,a.data.uri)}function execSyscmd(a){sendAPI({cmd:"MYMPD_API_SYSCMD",data:{cmd:a}})}function playlistDetails(a){document.getElementById("BrowsePlaylistsAllList").classList.add("opacity05");appGoto("Browse","Playlists","Detail","0/-/-/"+a)}function removeFromPlaylist(a,b){b--;sendAPI({cmd:"MPD_API_PLAYLIST_RM_TRACK",data:{uri:a,track:b}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")}
function playlistClear(){var a=document.getElementById("BrowsePlaylistsDetailList").getAttribute("data-uri");sendAPI({cmd:"MPD_API_PLAYLIST_CLEAR_AND_LIST",data:{uri:a}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")}
function getAllPlaylists(a){var b=a.data.length,c="";0==a.offset&&("addToPlaylistPlaylist"==playlistEl?c="<option></option><option>New Playlist</option>":"selectJukeboxPlaylist"==playlistEl&&(c="<option>Database</option>"));for(var d=0;d<b;d++)c+="<option","selectJukeboxPlaylist"==playlistEl&&a.data[d].uri==settings.jukeboxPlaylist&&(c+=" selected"),c+=">"+a.data[d].uri+"</option>";0==a.offset?document.getElementById(playlistEl).innerHTML=c:document.getElementById(playlistEl).innerHTML+=c;a.totalEntities>
a.returnedEntities&&(a.offset+=settings.maxElementsPerPage,sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:a.offset,filter:"-"}},getAllPlaylists))}function updateSmartPlaylists(){sendAPI({cmd:"MPD_API_SMARTPLS_UPDATE_ALL"})}
@ -189,13 +195,13 @@ function updateDBfinished(a){document.getElementById("modalUpdateDB").classList.
"update_database"==a?showNotification("Database successfully updated.","","","success"):"update_finished"==a&&showNotification("Database update finished.","","","success")}function clickPlay(){"play"!=playstate?sendAPI({cmd:"MPD_API_PLAYER_PLAY"}):sendAPI({cmd:"MPD_API_PLAYER_PAUSE"})}function clickStop(){sendAPI({cmd:"MPD_API_PLAYER_STOP"})}function clickPrev(){sendAPI({cmd:"MPD_API_PLAYER_PREV"})}function clickNext(){sendAPI({cmd:"MPD_API_PLAYER_NEXT"})}
function delQueueSong(a,b,c){"range"==a?sendAPI({cmd:"MPD_API_QUEUE_RM_RANGE",data:{start:b,end:c}}):"single"==a&&sendAPI({cmd:"MPD_API_QUEUE_RM_TRACK",data:{track:b}})}function showDelPlaylist(a){document.getElementById("deletePlaylist").value=a;modalDeletePlaylist.show()}function delPlaylist(){var a=document.getElementById("deletePlaylist").value;sendAPI({cmd:"MPD_API_PLAYLIST_RM",data:{uri:a}});modalDeletePlaylist.hide()}
function confirmSettings(){var a=!0,b=document.getElementById("inputCrossfade");if(!b.getAttribute("disabled")){var c=parseInt(b.value);isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c}b=document.getElementById("inputJukeboxQueueLength");c=parseInt(b.value);isNaN(c)?(b.classList.add("is-invalid"),a=!1):0<c?b.value=c:(b.classList.add("is-invalid"),a=!1);settings.mixramp&&(b=document.getElementById("inputMixrampdb"),b.getAttribute("disabled")||(c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),
a=!1):b.value=c),b=document.getElementById("inputMixrampdelay"),b.getAttribute("disabled")||("nan"==b.value&&(b.value="-1"),c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c));1==a?(a=document.getElementById("selectReplaygain"),c=document.getElementById("selectJukeboxPlaylist"),b=document.getElementById("selectJukeboxMode"),sendAPI({cmd:"MPD_API_SETTINGS_SET",data:{consume:document.getElementById("btnConsume").classList.contains("active")?1:0,random:document.getElementById("btnRandom").classList.contains("active")?
a=!1):b.value=c),b=document.getElementById("inputMixrampdelay"),b.getAttribute("disabled")||("nan"==b.value&&(b.value="-1"),c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c));1==a?(a=document.getElementById("selectReplaygain"),c=document.getElementById("selectJukeboxPlaylist"),b=document.getElementById("selectJukeboxMode"),sendAPI({cmd:"MYMPD_API_SETTINGS_SET",data:{consume:document.getElementById("btnConsume").classList.contains("active")?1:0,random:document.getElementById("btnRandom").classList.contains("active")?
1:0,single:document.getElementById("btnSingle").classList.contains("active")?1:0,repeat:document.getElementById("btnRepeat").classList.contains("active")?1:0,replaygain:a.options[a.selectedIndex].value,crossfade:document.getElementById("inputCrossfade").value,mixrampdb:1==settings.mixramp?document.getElementById("inputMixrampdb").value:settings.mixrampdb,mixrampdelay:1==settings.mixramp?document.getElementById("inputMixrampdelay").value:settings.mixrampdelay,notificationWeb:document.getElementById("btnnotifyWeb").classList.contains("active")?
!0:!1,notificationPage:document.getElementById("btnnotifyPage").classList.contains("active")?!0:!1,jukeboxMode:b.options[b.selectedIndex].value,jukeboxPlaylist:c.options[c.selectedIndex].value,jukeboxQueueLength:document.getElementById("inputJukeboxQueueLength").value}},getSettings),modalSettings.hide()):document.getElementById("settingsFrm").classList.add("was-validated")}
!0:!1,notificationPage:document.getElementById("btnnotifyPage").classList.contains("active")?!0:!1,jukeboxMode:b.options[b.selectedIndex].value,jukeboxPlaylist:c.options[c.selectedIndex].value,jukeboxQueueLength:document.getElementById("inputJukeboxQueueLength").value,autoPlay:document.getElementById("btnAutoPlay").classList.contains("active")?!0:!1}},getSettings),modalSettings.hide()):document.getElementById("settingsFrm").classList.add("was-validated")}
function addAllFromBrowseFilesystem(){sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}
function addAllFromSearchPlist(a){var b=parseInt(document.getElementById("panel-heading-search").innerText);!isNaN(b)&&0<b&&(settings.featAdvsearch?sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:a,sort:"",sortdesc:!1,expression:app.current.search,offset:0}}):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.filter,searchstr:app.current.search,offset:0}}),showNotification("Added "+b+" songs from search to "+a,"","","success"))}
function addAllFromBrowseDatabasePlist(a){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.view,searchstr:app.current.search,offset:0}}),showNotification("Added songs from database selection to "+a,"","","success"))}function scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a}
function gotoPage(a){switch(a){case "next":app.current.page+=settings.maxElementsPerPage;break;case "prev":app.current.page-=settings.maxElementsPerPage;0>app.current.page&&(app.current.page=0);break;default:app.current.page=a}appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.search)}
function gotoPage(a){switch(a){case "next":app.current.page+=settings.maxElementsPerPage;break;case "prev":app.current.page-=settings.maxElementsPerPage;0>app.current.page&&(app.current.page=0);break;default:app.current.page=a}appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.sort+"/"+app.current.search)}
function saveQueue(){var a=document.getElementById("saveQueueName").value,b=a.replace(/[\w\-]/g,"");""!=a&&""==b?(sendAPI({cmd:"MPD_API_QUEUE_SAVE",data:{plist:a}}),modalSavequeue.hide()):(alert(b),document.getElementById("saveQueueName").classList.add("is-invalid"),document.getElementById("saveQueueFrm").classList.add("was-validated"))}
function showNotification(a,b,c,d){1==settings.notificationWeb&&(b=new Notification(a,{icon:"assets/favicon.ico",body:b}),setTimeout(function(a){a.close()},3E3,b));1==settings.notificationPage&&(document.getElementById("alertBox")?b=document.getElementById("alertBox"):(b=document.createElement("div"),b.setAttribute("id","alertBox"),b.addEventListener("click",function(){hideNotification()},!1)),b.classList.remove("alert-success","alert-danger"),b.classList.add("alert","alert-"+d),b.innerHTML="<div><strong>"+
a+"</strong><br/>"+c+"</div>",document.getElementsByTagName("main")[0].append(b),document.getElementById("alertBox").classList.add("alertBoxActive"),alertTimeout&&clearTimeout(alertTimeout),alertTimeout=setTimeout(function(){hideNotification()},3E3))}function hideNotification(){document.getElementById("alertBox")&&(document.getElementById("alertBox").classList.remove("alertBoxActive"),setTimeout(function(){var a=document.getElementById("alertBox");a&&a.remove()},600))}
@ -205,7 +211,7 @@ function songChange(a){if("error"!=a.type&&"result"!=a.type){var b=a.data.Title+
a.data[settings.colsPlayback[e]];f.setAttribute("data-name",encodeURI(a.data[settings.colsPlayback[e]]))}if(e=document.getElementById("queueTrackId"+a.data.currentSongId))e.getElementsByTagName("td")[1].innerText=a.data.Title;"play"==playstate&&showNotification(a.data.Title,c,d,"success");lastSong=b;lastSongObj=a}}}
function doSetFilterLetter(a){var b=document.getElementById(a+"Letters").getElementsByClassName("active")[0];b&&b.classList.remove("active");b=app.current.filter;"0"==b&&(b="#");document.getElementById(a).innerText="Filter"+("-"!=b?": "+b:"");if("-"!=b){a=document.getElementById(a+"Letters").getElementsByTagName("button");for(var c=a.length,d=0;d<c;d++)if(a[d].innerText==b){a[d].classList.add("active");break}}}
function addFilterLetter(a){for(var b='<button class="mr-1 mb-1 btn btn-sm btn-secondary material-icons material-icons-small">delete</button><button class="mr-1 mb-1 btn btn-sm btn-secondary">#</button>',c=65;90>=c;c++)b+='<button class="mr-1 mb-1 btn-sm btn btn-secondary">'+String.fromCharCode(c)+"</button>";a=document.getElementById(a);a.innerHTML=b;a.addEventListener("click",function(a){switch(a.target.innerText){case "delete":b="-";break;case "#":b="0";break;default:b=a.target.innerText}appGoto(app.current.app,
app.current.tab,app.current.view,"0/"+b+"/"+app.current.search)},!1)}function selectTag(a,b,c){a=document.getElementById(a);var d=a.querySelector(".active");d&&d.classList.remove("active");if(d=a.querySelector("[data-tag="+c+"]"))d.classList.add("active"),document.getElementById(b).innerText=d.innerText}
app.current.tab,app.current.view,"0/"+b+"/"+app.current.sort+"/"+app.current.search)},!1)}function selectTag(a,b,c){a=document.getElementById(a);var d=a.querySelector(".active");d&&d.classList.remove("active");if(d=a.querySelector("[data-tag="+c+"]"))d.classList.add("active"),document.getElementById(b).innerText=d.innerText}
function addTagList(a,b){var c="";"searchtags"==b&&(1==settings.featTags&&(c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="any">Any Tag</button>'),c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="filename">Filename</button>');for(var d=0;d<settings[b].length;d++)c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="'+settings[b][d]+'">'+settings[b][d]+"</button>";document.getElementById(a).innerHTML=c}
function gotoTagList(){appGoto(app.current.app,app.current.tab,app.current.view,"0/-/")}function openModal(a){window[a].show()}function openDropdown(a){window[a].toggle()}function focusSearch(){"Queue"==app.current.app?document.getElementById("searchqueuestr").focus():"Search"==app.current.app?domCache.searchstr.focus():appGoto("Search")}
function chVolume(a){a=parseInt(domCache.volumeBar.value)+a;0>a?a=0:100<a&&(a=100);domCache.volumeBar.value=a;sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:a}})}function beautifyDuration(a){var b=Math.floor(a/86400),c=Math.floor(a/3600)-24*b,d=Math.floor(a/60)-60*c-1440*b;a=a-86400*b-3600*c-60*d;return(0<b?b+"\u2009d ":"")+(0<c?c+"\u2009h "+(10>d?"0":""):"")+d+"\u2009m "+(10>a?"0":"")+a+"\u2009s"}function genId(a){return"id"+a.replace(/[^\w\-]/g,"")}appInit();
function gotoTagList(){appGoto(app.current.app,app.current.tab,app.current.view,"0/-/-/")}function openModal(a){window[a].show()}function openDropdown(a){window[a].toggle()}function focusSearch(){"Queue"==app.current.app?document.getElementById("searchqueuestr").focus():"Search"==app.current.app?domCache.searchstr.focus():appGoto("Search")}
function chVolume(a){a=parseInt(domCache.volumeBar.value)+a;0>a?a=0:100<a&&(a=100);domCache.volumeBar.value=a;sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:a}})}function beautifyDuration(a){var b=Math.floor(a/86400),c=Math.floor(a/3600)-24*b,d=Math.floor(a/60)-60*c-1440*b;a=a-86400*b-3600*c-60*d;return(0<b?b+"\u2009d ":"")+(0<c?c+"\u2009h "+(10>d?"0":""):"")+d+"\u2009m "+(10>a?"0":"")+a+"\u2009s"}function genId(a){return"id"+a.replace(/[^\w\-]/g,"")}appInitStart();

View File

@ -10,5 +10,5 @@ function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=
void 0;try{d=a.then}catch(h){this.reject_(h);return}"function"==typeof d?this.settleSameAsThenable_(d,a):this.fulfill_(a)};c.prototype.reject_=function(a){this.settle_(2,a)};c.prototype.fulfill_=function(a){this.settle_(1,a)};c.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b+"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};c.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a=
0;a<this.onSettledCallbacks_.length;++a)l.asyncExecute(this.onSettledCallbacks_[a]);this.onSettledCallbacks_=null}};var l=new b;c.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};c.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(k){c.reject(k)}};c.prototype.then=function(a,b){function d(a,b){return"function"==typeof a?function(b){try{f(a(b))}catch(m){e(m)}}:
b}var f,e,g=new c(function(a,b){f=a;e=b});this.callWhenSettled_(d(a,f),d(b,e));return g};c.prototype.catch=function(a){return this.then(void 0,a)};c.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?l.asyncExecute(c):this.onSettledCallbacks_.push(c)};c.resolve=f;c.reject=function(a){return new c(function(b,c){c(a)})};c.race=function(a){return new c(function(b,
c){for(var d=$jscomp.makeIterator(a),e=d.next();!e.done;e=d.next())f(e.value).callWhenSettled_(b,c)})};c.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?f([]):new c(function(a,c){function e(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,f(d.value).callWhenSettled_(e(g.length-1),c),d=b.next();while(!d.done)})};return c},"es6","es3");var CACHE="myMPD-cache-v4.7.2",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mympd.min.css /js/bootstrap-native-v4.min.js /js/mympd.min.js /js/player.min.js /js/keymap.min.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/coverimage-loading.png /assets/favicon.ico /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" ");
c){for(var d=$jscomp.makeIterator(a),e=d.next();!e.done;e=d.next())f(e.value).callWhenSettled_(b,c)})};c.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?f([]):new c(function(a,c){function e(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,f(d.value).callWhenSettled_(e(g.length-1),c),d=b.next();while(!d.done)})};return c},"es6","es3");var CACHE="myMPD-cache-v5.0.0",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mympd.min.css /js/bootstrap-native-v4.min.js /js/mympd.min.js /js/player.min.js /js/keymap.min.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/coverimage-loading.png /assets/favicon.ico /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" ");
self.addEventListener("install",function(a){a.waitUntil(caches.open(CACHE).then(function(a){return a.addAll(urlsToCache)}))});self.addEventListener("fetch",function(a){if(a.request.url.match("^http://"))return!1;a.respondWith(caches.match(a.request).then(function(b){return b?b:fetch(a.request)}))});self.addEventListener("activate",function(a){a.waitUntil(caches.keys().then(function(a){return Promise.all(a.map(function(a){if(a!=CACHE)return caches.delete(a)}))}))});

View File

@ -4589,7 +4589,7 @@ struct mg_ssl_if_ctx {
size_t identity_len;
};
void mg_ssl_if_init() {
void mg_ssl_if_init(void) {
SSL_library_init();
}

View File

@ -428,7 +428,7 @@ unsigned int sleep(unsigned int seconds);
*/
#if !(defined(__cplusplus) && __cplusplus >= 201103L) && \
!(defined(__DARWIN_C_LEVEL) && __DARWIN_C_LEVEL >= 200809L)
long long strtoll(const char *, char **, int);
//long long strtoll(const char *, char **, int);
#endif
typedef int sock_t;
@ -3747,7 +3747,7 @@ extern "C" {
struct mg_ssl_if_ctx;
struct mg_connection;
void mg_ssl_if_init();
void mg_ssl_if_init(void);
enum mg_ssl_if_result {
MG_SSL_OK = 0,

View File

@ -1,5 +1,6 @@
:root {
--mympd-coverimagesize: 240px;
--mympd-bacgkroundcolor: #888;
}
html {
@ -8,13 +9,13 @@ html {
}
body {
padding-top: 50px;
padding-bottom: 50px;
background-color: #888;
padding-top: 4rem;
padding-bottom: 4rem;
background-color: var(--mympd-backgroundcolor, #888);
}
main {
padding-top: 10px;
padding-top: 1rem;
}
footer {
@ -51,7 +52,7 @@ button {
}
[data-col=Pos], [data-col=Type], [data-col=Track], [data-col=Action] {
width: 30px;
width: 2rem;
}
small {
@ -72,9 +73,9 @@ small {
border: 1px solid black;
border-radius: 5px;
overflow: hidden;
width: var(--mympd-coverimagesize);
width: var(--mympd-coverimagesize, 250px);
max-width:100%;
height: var(--mympd-coverimagesize);
height: var(--mympd-coverimagesize, 250px);
background-color: #eee;
float: left;
margin-right: 1.25rem;
@ -91,6 +92,10 @@ small {
display: none !important;
}
.unvisible {
visibility: hidden;
}
.pull-right {
float: right !important;
}
@ -116,7 +121,7 @@ small {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 18px; /* Preferred icon size */
font-size: 1.4rem; /* Preferred icon size */
display:inline-block;
line-height: 1;
text-transform: none;
@ -142,7 +147,7 @@ small {
}
.material-icons-small {
font-size: 16px;
font-size: 1rem;
}
.material-icons-small-left {
@ -167,7 +172,7 @@ small {
}
.progressBarPlay {
font-size: 22px;
font-size: 1.8rem;
}
#counter {
@ -230,8 +235,8 @@ button.active-fg-red {
div#alertBox {
position:fixed;
top: 50px;
right:10px;
top: 4rem;
right:1rem;
width:80%;
max-width:400px;
z-index:1000;
@ -327,7 +332,7 @@ div.key {
background-color: #eee;
border-radius: 2px;
width: 20px;
heigth: 20px;
height: 20px;
text-align: center;
}

View File

@ -16,7 +16,7 @@
<link rel="apple-touch-icon" href="/assets/appicon-167.png">
</head>
<body>
<header>
<header class="hide">
<nav class="navbar navbar-expand navbar-dark fixed-top bg-dark">
<div class="dropdown col-auto mr-auto pl-0">
<a class="dropdown-toggle navbar-brand" href="#" id="mainMenu">
@ -74,7 +74,7 @@
</div>
</nav>
</header>
<main class="container">
<main class="container hide">
<noscript>
<div class="alert alert-danger">JavaScript is disabled!</div>
</noscript>
@ -567,10 +567,10 @@
</div>
</div>
</div>
<ol class="FeatAdvsearch breadcrumb" id="searchCrumb"></ol>
<ol class="featAdvsearch breadcrumb" id="searchCrumb"></ol>
<div class="table-responsive-md">
<table id="SearchList" class="table table-hover table-sm" data-sort="">
<table id="SearchList" class="table table-hover table-sm">
<thead>
<tr>
<th></th>
@ -607,7 +607,7 @@
</div>
</main>
<footer class="footer">
<footer class="footer hide">
<nav class="navbar navbar-expand navbar-dark fixed-bottom bg-dark">
<div class="d-flex flex-fill navbar-nav" id="navbar-bottom">
<div id="navPlayback" class="nav-item flex-fill text-center"><a data-href='{"cmd": "appGoto", "options": ["Playback"]}' class="nav-link" href="#">Playback</a></div>
@ -619,6 +619,23 @@
</footer>
<!-- Modals -->
<div class="modal" id="modalAppInit" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-light">
<h5 class="modal-title"><span class="material-icons title-icon">play_circle_outline</span> Initializing myMPD</h5>
</div>
<div class="modal-body">
<p>Please wait, myMPD is initializing...</p>
<p><span id="appInitSettings" class="material-icons unvisible">check</span> Fetch settings</p>
<p><span id="appInitWebsocket" class="material-icons unvisible">check</span> Connect to websocket</p>
<p><span id="appInitApply" class="material-icons unvisible">check</span> Applying settings</p>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalConnectionError" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
@ -886,6 +903,13 @@
<div class="invalid-feedback">Must be a number.</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6" data-toggle="buttons">
<button data-href='{"cmd": "toggleBtn", "options": ["btnAutoPlay"]}' id="btnAutoPlay" type="button" class="btn btn-secondary btn-block" title="Start playing after first song is added to the queue">
AutoPlay
</button>
</div>
</div>
<hr/>
<h4>Jukebox</h4>
<div class="row">

View File

@ -40,7 +40,6 @@ var time_end = 0;
var time_all = 0;
var cmds = [
'{"cmd":"MPD_API_WELCOME"}',
'{"cmd":"MPD_API_QUEUE_CLEAR"}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":""}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}',
@ -64,10 +63,10 @@ var cmds = [
'{"cmd":"MPD_API_PLAYLIST_LIST","data":{"offset":0,"filter":""}}',
'{"cmd":"MPD_API_PLAYLIST_CLEAR","data":{"uri":"test2"}}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":"test2"}}',
'{"cmd":"MPD_API_QUEUE_CROP"}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI2__"}}',
'{"cmd":"MPD_API_PLAYER_PLAY"}',
'{"cmd":"MPD_API_QUEUE_CROP"}',
'{"cmd":"MPD_API_QUEUE_LAST_PLAYED","data":{"offset":0}}',
'{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}',
'{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}',
@ -93,11 +92,12 @@ var cmds = [
'{"cmd":"MPD_API_PLAYER_OUTPUT_LIST"}',
'{"cmd":"MPD_API_PLAYER_TOGGLE_OUTPUT","data":{"output":"__OUTPUTID__","state":1}}',
'{"cmd":"MPD_API_PLAYER_STATE"}',
'{"cmd":"MYMPD_API_SETTINGS_GET"}',
'{"cmd":"MPD_API_SETTINGS_GET"}',
'{"cmd":"MPD_API_SETTINGS_SET","data":{"random": 0}}',
'{"cmd":"MYMPD_API_SETTINGS_SET","data":{"random": 0}}',
'{"cmd":"MPD_API_LIKE","data":{"uri":"__URI2__","like":2}}',
'{"cmd":"MPD_API_SYSCMD","data":{"cmd": "Echo"}}',
'{"cmd":"MPD_API_COLS_SAVE","data":{"table":"colsPlayback","cols":["Artist","Album","AlbumArtist"]}}',
'{"cmd":"MYMPD_API_SYSCMD","data":{"cmd": "Echo"}}',
'{"cmd":"MYMPD_API_COLS_SAVE","data":{"table":"colsPlayback","cols":["Artist","Album","AlbumArtist"]}}',
'{"cmd":"MPD_API_SMARTPLS_UPDATE_ALL"}',
'{"cmd":"MPD_API_SMARTPLS_SAVE","data":{"type":"newest","playlist":"myMPDsmart-newestSongs","timerange":2678400}}',
'{"cmd":"MPD_API_SMARTPLS_GET","data":{"playlist":"myMPDsmart-newestSongs"}}'

View File

@ -29,28 +29,34 @@ var lastSongObj = {};
var lastState;
var currentSong = new Object();
var playstate = '';
var settingsLock = false;
var settingsParsed = false;
var settingsNew = {};
var settings = {};
var alertTimeout;
var progressTimer;
var deferredPrompt;
var dragEl;
var playlistEl;
var websocketConnected = false;
var websocketTimer = null;
var appInited = false;
var app = {};
app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 },
app.apps = { "Playback": { "state": "0/-/-/", "scrollPos": 0 },
"Queue": {
"active": "Current",
"tabs": { "Current": { "state": "0/any/", "scrollPos": 0 },
"LastPlayed": { "state": "0/any/", "scrollPos": 0 }
"tabs": { "Current": { "state": "0/any/-/", "scrollPos": 0 },
"LastPlayed": { "state": "0/any/-/", "scrollPos": 0 }
}
},
"Browse": {
"active": "Database",
"tabs": { "Filesystem": { "state": "0/-/", "scrollPos": 0 },
"tabs": { "Filesystem": { "state": "0/-/-/", "scrollPos": 0 },
"Playlists": {
"active": "All",
"views": { "All": { "state": "0/-/", "scrollPos": 0 },
"Detail": { "state": "0/-/", "scrollPos": 0 }
"views": { "All": { "state": "0/-/-/", "scrollPos": 0 },
"Detail": { "state": "0/-/-/", "scrollPos": 0 }
}
},
"Database": {
@ -60,11 +66,11 @@ app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 },
}
}
},
"Search": { "state": "0/any/", "scrollPos": 0 }
"Search": { "state": "0/any/-/", "scrollPos": 0 }
};
app.current = { "app": "Playback", "tab": undefined, "view": undefined, "page": 0, "filter": "", "search": "", "scrollPos": 0 };
app.last = { "app": undefined, "tab": undefined, "view": undefined, "filter": "", "search": "", "scrollPos": 0 };
app.current = { "app": "Playback", "tab": undefined, "view": undefined, "page": 0, "filter": "", "search": "", "sort": "", "scrollPos": 0 };
app.last = { "app": undefined, "tab": undefined, "view": undefined, "filter": "", "search": "", "sort": "", "scrollPos": 0 };
var domCache = {};
domCache.navbarBottomBtns = document.getElementById('navbar-bottom').getElementsByTagName('div');
@ -104,6 +110,7 @@ var modalUpdateDB = new Modal(document.getElementById('modalUpdateDB'));
var modalSaveSmartPlaylist = new Modal(document.getElementById('modalSaveSmartPlaylist'));
var modalDeletePlaylist = new Modal(document.getElementById('modalDeletePlaylist'));
var modalHelp = new Modal(document.getElementById('modalHelp'));
var modalAppInit = new Modal(document.getElementById('modalAppInit'), { backdrop: 'static', keyboard: false});
var dropdownMainMenu;
var dropdownVolumeMenu = new Dropdown(document.getElementById('volumeMenu'));
@ -178,9 +185,13 @@ function appGoto(a,t,v,s) {
}
function appRoute() {
if (settingsParsed == false) {
appInitStart();
return;
}
var hash = decodeURI(location.hash);
var params;
if (params = hash.match(/^\#\/(\w+)\/?(\w+)?\/?(\w+)?\!((\d+)\/([^\/]+)\/(.*))$/)) {
if (params = hash.match(/^\#\/(\w+)\/?(\w+)?\/?(\w+)?\!((\d+)\/([^\/]+)\/([^\/]+)\/(.*))$/)) {
app.current.app = params[1];
app.current.tab = params[2];
app.current.view = params[3];
@ -201,7 +212,8 @@ function appRoute() {
}
app.current.page = parseInt(params[5]);
app.current.filter = params[6];
app.current.search = params[7];
app.current.sort = params[7];
app.current.search = params[8];
}
else {
appGoto('Playback');
@ -270,7 +282,7 @@ function appRoute() {
var breadcrumbItemsLen = breadcrumbItems.length;
for (var i = 0; i < breadcrumbItemsLen; i++) {
breadcrumbItems[i].addEventListener('click', function() {
appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter + '/' + this.getAttribute('data-uri'));
appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + this.getAttribute('data-uri'));
}, false);
}
doSetFilterLetter('BrowseFilesystemFilter');
@ -313,13 +325,13 @@ function appRoute() {
if (domCache.searchstr.value.length >= 2 || domCache.searchCrumb.children.length > 0) {
if (settings.featAdvsearch) {
var sort = document.getElementById('SearchList').getAttribute('data-sort');
var sort = app.current.sort;//document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = false;
if (sort == '') {
if (sort == '-') {
if (settings.tags.includes('Title'))
sort = 'Title';
else
sort = '';
sort = '-';
document.getElementById('SearchList').setAttribute('data-sort', sort);
}
else {
@ -353,10 +365,45 @@ function appRoute() {
app.last.view = app.current.view;
};
function appInit() {
webSocketConnect();
function appInitStart() {
appInited = false;
document.getElementsByTagName('header')[0].classList.add('hide');
document.getElementsByTagName('main')[0].classList.add('hide');
document.getElementsByTagName('footer')[0].classList.add('hide');
document.getElementById('appInitSettings').classList.add('unvisible');
document.getElementById('appInitWebsocket').classList.add('unvisible');
document.getElementById('appInitApply').classList.add('unvisible');
modalAppInit.show();
getSettings();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
appInitWait();
}
function appInitWait() {
setTimeout(function() {
if (settingsParsed == true && websocketConnected == true) {
//app initialized
document.getElementById('appInitWebsocket').classList.remove('unvisible');
appInit();
document.getElementById('appInitApply').classList.remove('unvisible');
document.getElementsByTagName('header')[0].classList.remove('hide');
document.getElementsByTagName('main')[0].classList.remove('hide');
document.getElementsByTagName('footer')[0].classList.remove('hide');
modalAppInit.hide();
appInited = true;
return;
}
if (settingsParsed == true) {
//parsed settings, now its save to connect to websocket
document.getElementById('appInitSettings').classList.remove('unvisible');
webSocketConnect();
}
appInitWait();
}, 500);
}
function appInit() {
// sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
domCache.volumeBar.value = 0;
@ -530,7 +577,7 @@ function appInit() {
if (event.target.nodeName == 'TD') {
switch(event.target.parentNode.getAttribute('data-type')) {
case 'dir':
appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter +'/' + decodeURI(event.target.parentNode.getAttribute("data-uri")));
appGoto('Browse', 'Filesystem', undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + decodeURI(event.target.parentNode.getAttribute("data-uri")));
break;
case 'song':
appendQueue('song', decodeURI(event.target.parentNode.getAttribute("data-uri")), event.target.parentNode.getAttribute("data-name"));
@ -565,7 +612,7 @@ function appInit() {
document.getElementById('BrowseDatabaseTagList').addEventListener('click', function(event) {
if (event.target.nodeName == 'TD') {
appGoto('Browse', 'Database', app.current.view, '0/-/' + event.target.parentNode.getAttribute('data-uri'));
appGoto('Browse', 'Database', app.current.view, '0/-/-/' + event.target.parentNode.getAttribute('data-uri'));
}
}, false);
@ -625,12 +672,12 @@ function appInit() {
if (event.key == 'Escape')
this.blur();
else
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + app.current.filter + '/' + this.value);
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + app.current.filter + '/' + app.current.sort + '/' + this.value);
}, false);
document.getElementById('searchqueuetags').addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON')
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + event.target.getAttribute('data-tag') + '/' + app.current.search);
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + event.target.getAttribute('data-tag') + '/' + app.current.sort + '/' + app.current.search);
}, false);
var dropdowns = ['QueueCurrentColsDropdown', 'BrowseFilesystemColsDropdown', 'SearchColsDropdown', 'BrowsePlaylistsDetailColsDropdown',
@ -700,7 +747,7 @@ function appInit() {
var col = event.target.getAttribute('data-col');
if (col == 'Duration')
return;
var sortcol = document.getElementById('SearchList').getAttribute('data-sort');
var sortcol = app.current.sort; //document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = true;
if (sortcol == col || sortcol == '-' + col) {
@ -723,16 +770,18 @@ function appInit() {
var s = document.getElementById('SearchList').getElementsByClassName('sort-dir');
for (var i = 0; i < s.length; i++)
s[i].remove();
document.getElementById('SearchList').setAttribute('data-sort', sortcol);
//document.getElementById('SearchList').setAttribute('data-sort', sortcol);
app.current.sort = sortcol;
event.target.innerHTML = col + '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
appRoute();
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search);
//appRoute();
}
}
}, false);
document.getElementById('BrowseDatabaseByTagDropdown').addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON')
appGoto(app.current.app, app.current.tab, event.target.getAttribute('data-tag') , '0/' + app.current.filter + '/' + app.current.search);
appGoto(app.current.app, app.current.tab, event.target.getAttribute('data-tag') , '0/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search);
}, false);
document.getElementsByTagName('body')[0].addEventListener('click', function(event) {
@ -811,6 +860,12 @@ function appInit() {
window.addEventListener('appinstalled', function(event) {
console.log('myMPD installed as app');
});
window.addEventListener('beforeunload', function() {
socket.onclose = function () {}; // disable onclose handler first
socket.close();
websocketConnected = false;
});
}
function parseCmd(event, href) {
@ -846,10 +901,10 @@ function search(x) {
expression += ')';
if (expression.length <= 2)
expression = '';
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + encodeURI(expression));
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + encodeURI(expression));
}
else
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + x);
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + app.current.sort + '/' + x);
}
function dragAndDropTable(table) {
@ -1010,17 +1065,10 @@ function webSocketConnect() {
try {
socket.onopen = function() {
console.log('connected');
showNotification('Connected to myMPD: ' + wsUrl, '', '', 'success');
modalConnectionError.hide();
appRoute();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
console.log('Websocket is connected');
}
socket.onmessage = function got_packet(msg) {
if (msg.data === lastState || msg.data.length == 0)
return;
try {
var obj = JSON.parse(msg.data);
} catch(e) {
@ -1028,6 +1076,13 @@ function webSocketConnect() {
}
switch (obj.type) {
case 'welcome':
websocketConnected = true;
showNotification('Connected to myMPD: ' + wsUrl, '', '', 'success');
modalConnectionError.hide();
appRoute();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
break;
case 'update_state':
parseState(obj);
break;
@ -1070,10 +1125,17 @@ function webSocketConnect() {
}
socket.onclose = function(){
console.log('disconnected');
modalConnectionError.show();
setTimeout(function() {
console.log('reconnect');
console.log('Websocket is disconnected');
if (appInited == true) {
//Show modal only if websocket was already connected before
modalConnectionError.show();
}
websocketConnected = false;
if (websocketTimer != null) {
clearTimeout(websocketTimer);
}
websocketTimer = setTimeout(function() {
console.log('Reconnecting websocket');
webSocketConnect();
}, 3000);
}
@ -1145,13 +1207,12 @@ function filterCols(x) {
settings[x] = cols;
}
function parseSettings(obj) {
settings = obj.data;
function parseSettings() {
toggleBtn('btnRandom', settings.random);
toggleBtn('btnConsume', settings.consume);
toggleBtn('btnSingle', settings.single);
toggleBtn('btnRepeat', settings.repeat);
toggleBtn('btnAutoPlay', settings.autoPlay);
if (settings.crossfade != undefined) {
document.getElementById('inputCrossfade').removeAttribute('disabled');
@ -1202,6 +1263,7 @@ function parseSettings(obj) {
var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage", "featAdvsearch"];
document.documentElement.style.setProperty('--mympd-coverimagesize', settings.coverimagesize + "px");
document.documentElement.style.setProperty('--mympd-backgroundcolor', settings.backgroundcolor);
for (var j = 0; j < features.length; j++) {
var Els = document.getElementsByClassName(features[j]);
@ -1213,8 +1275,8 @@ function parseSettings(obj) {
if (settings.featTags == false) {
app.apps.Browse.active = 'Filesystem';
app.apps.Search.state = '0/filename/';
app.apps.Queue.state = '0/filename/';
app.apps.Search.state = '0/filename/-/';
app.apps.Queue.state = '0/filename/-/';
settings.colsQueueCurrent = ["Pos", "Title", "Duration"];
settings.colsQueueLastPlayed = ["Pos", "Title", "LastPlayed"];
settings.colsSearch = ["Title", "Duration"];
@ -1247,6 +1309,9 @@ function parseSettings(obj) {
else
app.apps.Browse.tabs.Database.active = settings.tags[0];
}
if (settings.tags.includes('Title')) {
app.apps.Search.state = '0/any/Title/';
}
document.getElementById('selectJukeboxMode').value = settings.jukeboxMode;
document.getElementById('inputJukeboxQueueLength').value = settings.jukeboxQueueLength;
@ -1295,17 +1360,17 @@ function parseSettings(obj) {
addTagList('searchtags', 'searchtags');
for (var i = 0; i < settings.tags.length; i++)
app.apps.Browse.tabs.Database.views[settings.tags[i]] = { "state": "0/-/", "scrollPos": 0 };
app.apps.Browse.tabs.Database.views[settings.tags[i]] = { "state": "0/-/-/", "scrollPos": 0 };
if (settings.featSyscmds) {
var mainMenuDropdown = document.getElementById('mainMenuDropdown');
var syscmdsList = '';
var syscmdsListLen = settings.syscmds.length;
var syscmdsListLen = settings.syscmdList.length;
if (syscmdsListLen > 0) {
syscmdsList = '<div class="dropdown-divider"></div>';
for (var i = 0; i < syscmdsListLen; i++) {
syscmdsList += '<a class="dropdown-item text-light bg-dark" href="#" data-href=\'{"cmd": "execSyscmd", "options": ["' +
settings.syscmds[i] + '"]}\'>' + settings.syscmds[i] + '</a>';
settings.syscmdList[i] + '"]}\'>' + settings.syscmdList[i] + '</a>';
}
}
document.getElementById('syscmds').innerHTML = syscmdsList;
@ -1333,6 +1398,8 @@ function parseSettings(obj) {
appRoute();
else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.search != '')
appRoute();
settingsParsed = true;
}
function setCols(table, className) {
@ -1362,14 +1429,23 @@ function setCols(table, className) {
'</div>';
}
document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks;
var sort = app.current.sort;
var sort = document.getElementById('SearchList').getAttribute('data-sort');
if (sort == '') {
if (settings.featTags)
sort = 'Title';
else
sort = 'Filename';
if (table == 'Search') {
if (app.apps.Search.state == '0/any/Title/') {
if (settings.tags.includes('Title')) {
sort = 'Title';
}
else if (settings.featTags == false) {
sort = 'Filename';
}
else {
sort = '-';
}
}
}
if (table != 'Playback') {
var heading = '';
@ -1380,11 +1456,10 @@ function setCols(table, className) {
h = '#';
heading += h;
if (table == 'Search' && h == sort ) {
if (table == 'Search' && (h == sort || '-' + h == sort) ) {
var sortdesc = false;
if (sort.indexOf('-') == 0) {
if (app.current.sort.indexOf('-') == 0) {
sortdesc = true;
sort = sort.substring(1);
}
heading += '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
}
@ -1404,7 +1479,24 @@ function setCols(table, className) {
}
function getSettings() {
sendAPI({"cmd": "MPD_API_SETTINGS_GET"}, parseSettings);
if (settingsLock == false) {
settingsLock = true;
sendAPI({"cmd": "MYMPD_API_SETTINGS_GET"}, getMpdSettings);
}
}
function getMpdSettings(obj) {
settingsNew = obj.data;
sendAPI({"cmd": "MPD_API_SETTINGS_GET"}, joinSettings);
}
function joinSettings(obj) {
for (var key in obj.data) {
settingsNew[key] = obj.data[key];
}
settings = Object.assign({}, settingsNew);
settingsLock = false;
parseSettings();
}
function saveCols(table, tableEl) {
@ -1431,7 +1523,7 @@ function saveCols(table, tableEl) {
}
}
var cols = {"cmd": "MPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}};
var cols = {"cmd": "MYMPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}};
var ths = header.getElementsByTagName('th');
for (var i = 0; i < ths.length; i++) {
var name = ths[i].getAttribute('data-col');
@ -1460,7 +1552,7 @@ function saveColsPlayback(table) {
}
}
var cols = {"cmd": "MPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}};
var cols = {"cmd": "MYMPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}};
var ths = header.getElementsByTagName('div');
for (var i = 0; i < ths.length; i++) {
var name = ths[i].getAttribute('data-tag');
@ -1585,7 +1677,7 @@ function parseState(obj) {
setCounter(obj.data.currentSongId, obj.data.totalTime, obj.data.elapsedTime);
//Get current song
if (lastState && lastState.data.currentSongId != obj.data.currentSongId)
if (!lastState || lastState.data.currentSongId != obj.data.currentSongId)
sendAPI({"cmd": "MPD_API_PLAYER_CURRENT_SONG"}, songChange);
//clear playback card if not playing
if (obj.data.songPos == '-1') {
@ -2182,7 +2274,7 @@ function gotoBrowse(x) {
var tag = x.parentNode.getAttribute('data-tag');
var name = decodeURI(x.parentNode.getAttribute('data-name'));
if (tag != '' && name != '' && name != '-' && settings.browsetags.includes(tag))
appGoto('Browse', 'Database', tag, '0/-/' + name);
appGoto('Browse', 'Database', tag, '0/-/-/' + name);
}
function songDetails(uri) {
@ -2232,12 +2324,12 @@ function parseSongDetails(obj) {
}
function execSyscmd(cmd) {
sendAPI({"cmd": "MPD_API_SYSCMD", "data": {"cmd": cmd}});
sendAPI({"cmd": "MYMPD_API_SYSCMD", "data": {"cmd": cmd}});
}
function playlistDetails(uri) {
document.getElementById('BrowsePlaylistsAllList').classList.add('opacity05');
appGoto('Browse', 'Playlists', 'Detail', '0/-/' + uri);
appGoto('Browse', 'Playlists', 'Detail', '0/-/-/' + uri);
}
function removeFromPlaylist(uri, pos) {
@ -2865,7 +2957,7 @@ function confirmSettings() {
var selectReplaygain = document.getElementById('selectReplaygain');
var selectJukeboxPlaylist = document.getElementById('selectJukeboxPlaylist');
var selectJukeboxMode = document.getElementById('selectJukeboxMode');
sendAPI({"cmd": "MPD_API_SETTINGS_SET", "data": {
sendAPI({"cmd": "MYMPD_API_SETTINGS_SET", "data": {
"consume": (document.getElementById('btnConsume').classList.contains('active') ? 1 : 0),
"random": (document.getElementById('btnRandom').classList.contains('active') ? 1 : 0),
"single": (document.getElementById('btnSingle').classList.contains('active') ? 1 : 0),
@ -2878,7 +2970,8 @@ function confirmSettings() {
"notificationPage": (document.getElementById('btnnotifyPage').classList.contains('active') ? true : false),
"jukeboxMode": selectJukeboxMode.options[selectJukeboxMode.selectedIndex].value,
"jukeboxPlaylist": selectJukeboxPlaylist.options[selectJukeboxPlaylist.selectedIndex].value,
"jukeboxQueueLength": document.getElementById('inputJukeboxQueueLength').value
"jukeboxQueueLength": document.getElementById('inputJukeboxQueueLength').value,
"autoPlay": (document.getElementById('btnAutoPlay').classList.contains('active') ? true : false)
}}, getSettings);
modalSettings.hide();
} else
@ -2926,7 +3019,7 @@ function gotoPage(x) {
default:
app.current.page = x;
}
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.search);
appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search);
}
function saveQueue() {
@ -3087,7 +3180,7 @@ function addFilterLetter(x) {
default:
filter = event.target.innerText;
}
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + filter + '/' + app.current.search);
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + filter + '/' + app.current.sort + '/' + app.current.search);
}, false);
}
@ -3116,7 +3209,7 @@ function addTagList(el, list) {
}
function gotoTagList() {
appGoto(app.current.app, app.current.tab, app.current.view, '0/-/');
appGoto(app.current.app, app.current.tab, app.current.view, '0/-/-/');
}
function openModal(modal) {
@ -3162,4 +3255,4 @@ function genId(x) {
}
//Init app
appInit();
appInitStart();

View File

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

View File

@ -20,7 +20,7 @@ fi
echo "Linking pics directory"
[ -e $PWD/htdocs/pics ] || ln -s /var/lib/mympd/pics htdocs/
[ -d debug ] || mkdir debug
install -d debug
cd debug
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=DEBUG ..
make
make VERBOSE=1

View File

@ -1,62 +1,77 @@
#/bin/sh
#!/bin/bash
java=$(which java 2> /dev/null)
JAVABIN=$(which java 2> /dev/null)
HASJAVA="$?"
if [ -f dist/buildtools/closure-compiler.jar ] && [ "$java" != "" ]
then
echo "Minifying javascript"
[ htdocs/js/player.js -nt dist/htdocs/js/player.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/player.js > dist/htdocs/js/player.min.js
[ htdocs/js/mympd.js -nt dist/htdocs/js/mympd.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/mympd.js > dist/htdocs/js/mympd.min.js
[ htdocs/sw.js -nt dist/htdocs/sw.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/sw.js > dist/htdocs/sw.min.js
[ htdocs/js/keymap.js -nt dist/htdocs/js/keymap.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/keymap.js > dist/htdocs/js/keymap.min.js
[ htdocs/js/keymap.js -nt dist/htdocs/js/keymap.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar htdocs/js/keymap.js > dist/htdocs/js/keymap.min.js
[ dist/htdocs/js/bootstrap-native-v4.js -nt dist/htdocs/js/bootstrap-native-v4.min.js ] && \
java -jar dist/buildtools/closure-compiler.jar dist/htdocs/js/bootstrap-native-v4.js > dist/htdocs/js/bootstrap-native-v4.min.js
else
echo "dist/buildtools/closure-compiler.jar not found, using non-minified files"
[ htdocs/js/player.js -nt dist/htdocs/js/player.min.js ] && \
cp htdocs/js/player.js dist/htdocs/js/player.min.js
[ htdocs/js/mympd.js -nt dist/htdocs/js/mympd.min.js ] && \
cp htdocs/js/mympd.js dist/htdocs/js/mympd.min.js
[ htdocs/sw.js -nt dist/htdocs/sw.min.js ] && \
cp htdocs/sw.js dist/htdocs/sw.min.js
[ htdocs/js/keymap.js -nt dist/htdocs/js/keymap.min.js ] && \
cp htdocs/js/keymap.js dist/htdocs/js/keymap.min.js
[ dist/htdocs/js/bootstrap-native-v4.js -nt dist/htdocs/js/bootstrap-native-v4.min.js ] && \
cp dist/htdocs/js/bootstrap-native-v4.js dist/htdocs/js/bootstrap-native-v4.min.js
fi
function minify {
TYPE="$1"
SRC="$2"
DST="$3"
ERROR="1"
if [ -f dist/buildtools/closure-stylesheets.jar ] && [ "$java" != "" ]
then
echo "Minifying stylesheets"
[ htdocs/css/mympd.css -nt dist/htdocs/css/mympd.min.css ] && \
java -jar dist/buildtools/closure-stylesheets.jar --allow-unrecognized-properties htdocs/css/mympd.css > dist/htdocs/css/mympd.min.css
else
echo "dist/buildtools/closure-stylesheets.jar not found, using non-minified files"
[ htdocs/css/mympd.css -nt dist/htdocs/css/mympd.min.css ] && \
cp htdocs/css/mympd.css dist/htdocs/css/mympd.min.css
fi
if [ "$DST" -nt "$SRC" ]
then
return
fi
if [ "$TYPE" = "html" ]
then
perl -pe 's/^\s*//gm; s/\s*$//gm' $SRC > $DST
ERROR="$?"
elif [ "$TYPE" = "js" ] && [ "$HASJAVA" = "0" ]
then
$JAVABIN -jar dist/buildtools/closure-compiler.jar $SRC > $DST
ERROR="$?"
elif [ "$TYPE" = "css" ] && [ "$HASJAVA" = "0" ]
then
$JAVABIN -jar dist/buildtools/closure-stylesheets.jar --allow-unrecognized-properties $SRC > $DST
ERROR="$?"
elif [ "$TYPE" = "cp" ]
then
cp $SRC $DST
ERROR="$?"
else
ERROR="1"
fi
if [ "$ERROR" = "1" ]
then
echo "Error minifying $SRC, copy $SRC to $DST"
cp $SRC $DST
fi
}
echo "Minifying javascript"
minify js htdocs/js/player.js dist/htdocs/js/player.min.js
minify js htdocs/js/mympd.js dist/htdocs/js/mympd.min.js
minify js htdocs/sw.js dist/htdocs/sw.min.js
minify js htdocs/js/keymap.js dist/htdocs/js/keymap.min.js
minify js htdocs/js/keymap.js dist/htdocs/js/keymap.min.js
minify js dist/htdocs/js/bootstrap-native-v4.js dist/htdocs/js/bootstrap-native-v4.min.js
echo "Minifying stylesheets"
minify css htdocs/css/mympd.css dist/htdocs/css/mympd.min.css
echo "Minifying html"
[ htdocs/index.html -nt dist/htdocs/index.html ] && \
perl -pe 's/^\s*//gm; s/\s*$//gm' htdocs/index.html > dist/htdocs/index.html
[ htdocs/player.html -nt dist/htdocs/player.html ] && \
perl -pe 's/^\s*//gm; s/\s*$//gm' htdocs/player.html > dist/htdocs/player.html
minify html htdocs/index.html dist/htdocs/index.html
minify html htdocs/player.html dist/htdocs/player.html
echo "Compiling and installing mympd"
[ -d release ] || mkdir release
install -d release
cd release
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=RELEASE ..
INSTALL_PREFIX="${MYMPD_INSTALL_PREFIX:-/usr}"
cmake -DCMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=RELEASE ..
make
sudo make install
cd ..
sudo debian/postinst
if [ $INSTALL_PREFIX = "/usr" ]
then
sudo make install
cd ..
sudo debian/postinst
else
# Container build implied when $INSTALL_PREFIX != /usr
make install
cd ..
fi
if [ -x /usr/bin/cppcheck ]
then

93
src/global.c Normal file
View File

@ -0,0 +1,93 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <dirent.h>
#include "tiny_queue.h"
#include "list.h"
#include "global.h"
bool testdir(const char *name, const char *dirname) {
DIR* dir = opendir(dirname);
if (dir) {
closedir(dir);
LOG_INFO() printf("%s: \"%s\"\n", name, dirname);
return true;
}
else {
printf("%s: \"%s\" don't exists\n", name, dirname);
return false;
}
}
int randrange(int n) {
return rand() / (RAND_MAX / (n + 1) + 1);
}
bool validate_string(const char *data) {
static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890_-. ";
const char *cp = data;
const char *end = data + strlen(data);
for (cp += strspn(cp, ok_chars); cp != end; cp += strspn(cp, ok_chars)) {
printf("ERROR: Invalid character in string\n");
return false;
}
return true;
}
int replacechar(char *str, const char orig, const char rep) {
char *ix = str;
int n = 0;
while ((ix = strchr(ix, orig)) != NULL) {
*ix++ = rep;
n++;
}
return n;
}
int copy_string(char * const dest, char const * const src, size_t const dst_len, size_t const src_len) {
if (dst_len == 0 || src_len == 0)
return 0;
size_t const max = (src_len < dst_len) ? src_len : dst_len -1;
memcpy(dest, src, max);
dest[max] = '\0';
return max;
}
enum mympd_cmd_ids get_cmd_id(const char *cmd) {
const char * mympd_cmd_strs[] = { MYMPD_CMDS(GEN_STR) };
for (unsigned i = 0; i < sizeof(mympd_cmd_strs) / sizeof(mympd_cmd_strs[0]); i++)
if (!strncmp(cmd, mympd_cmd_strs[i], strlen(mympd_cmd_strs[i])))
return i;
return 0;
}

197
src/global.h.in Normal file
View File

@ -0,0 +1,197 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
#include <signal.h>
//architecture
#cmakedefine PKGARCH64
//myMPD version from cmake
#define MYMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}
#define MYMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}
#define MYMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
#define MYMPD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}"
//Webserver document root
#define DOC_ROOT "${ASSETS_PATH}"
//Max size of mpd_client response buffer
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
//central logging definition
#cmakedefine DEBUG
#define LOG_INFO() if (loglevel >= 1)
#define LOG_VERBOSE() if (loglevel >= 2)
#define LOG_DEBUG() if (loglevel == 3)
//check and return buffer size
#define CHECK_RETURN_LEN() do { \
if (len > MAX_SIZE) \
printf("ERROR: Buffer truncated %d / %d\n", (int)len, MAX_SIZE); \
return len; \
} while (0)
//API cmds
#define MYMPD_CMDS(X) \
X(MPD_API_UNKNOWN) \
X(MPD_API_QUEUE_CLEAR) \
X(MPD_API_QUEUE_CROP) \
X(MPD_API_QUEUE_SAVE) \
X(MPD_API_QUEUE_LIST) \
X(MPD_API_QUEUE_SEARCH) \
X(MPD_API_QUEUE_RM_TRACK) \
X(MPD_API_QUEUE_RM_RANGE) \
X(MPD_API_QUEUE_MOVE_TRACK) \
X(MPD_API_QUEUE_ADD_TRACK_AFTER) \
X(MPD_API_QUEUE_ADD_TRACK) \
X(MPD_API_QUEUE_ADD_PLAY_TRACK) \
X(MPD_API_QUEUE_REPLACE_TRACK) \
X(MPD_API_QUEUE_ADD_PLAYLIST) \
X(MPD_API_QUEUE_REPLACE_PLAYLIST) \
X(MPD_API_QUEUE_SHUFFLE) \
X(MPD_API_QUEUE_LAST_PLAYED) \
X(MPD_API_PLAYLIST_CLEAR) \
X(MPD_API_PLAYLIST_RENAME) \
X(MPD_API_PLAYLIST_MOVE_TRACK) \
X(MPD_API_PLAYLIST_ADD_TRACK) \
X(MPD_API_PLAYLIST_RM_TRACK) \
X(MPD_API_PLAYLIST_RM) \
X(MPD_API_PLAYLIST_LIST) \
X(MPD_API_PLAYLIST_CONTENT_LIST) \
X(MPD_API_SMARTPLS_UPDATE_ALL) \
X(MPD_API_SMARTPLS_SAVE) \
X(MPD_API_SMARTPLS_GET) \
X(MPD_API_DATABASE_SEARCH_ADV) \
X(MPD_API_DATABASE_SEARCH) \
X(MPD_API_DATABASE_UPDATE) \
X(MPD_API_DATABASE_RESCAN) \
X(MPD_API_DATABASE_FILESYSTEM_LIST) \
X(MPD_API_DATABASE_TAG_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST) \
X(MPD_API_DATABASE_STATS) \
X(MPD_API_DATABASE_SONGDETAILS) \
X(MPD_API_PLAYER_PLAY_TRACK) \
X(MPD_API_PLAYER_VOLUME_SET) \
X(MPD_API_PLAYER_VOLUME_GET) \
X(MPD_API_PLAYER_PAUSE) \
X(MPD_API_PLAYER_PLAY) \
X(MPD_API_PLAYER_STOP) \
X(MPD_API_PLAYER_SEEK) \
X(MPD_API_PLAYER_NEXT) \
X(MPD_API_PLAYER_PREV) \
X(MPD_API_PLAYER_OUTPUT_LIST) \
X(MPD_API_PLAYER_TOGGLE_OUTPUT) \
X(MPD_API_PLAYER_CURRENT_SONG) \
X(MPD_API_PLAYER_STATE) \
X(MPD_API_SETTINGS_GET) \
X(MPD_API_LIKE) \
X(MYMPD_API_COLS_SAVE) \
X(MYMPD_API_SYSCMD) \
X(MYMPD_API_SETTINGS_GET) \
X(MYMPD_API_SETTINGS_SET) \
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
//Global variables
int loglevel;
//signal handler
sig_atomic_t s_signal_received;
enum mympd_cmd_ids {
MYMPD_CMDS(GEN_ENUM)
};
enum jukebox_modes {
JUKEBOX_OFF,
JUKEBOX_ADD_SONG,
JUKEBOX_ADD_ALBUM,
};
//message queue
tiny_queue_t *web_server_queue;
tiny_queue_t *mpd_client_queue;
tiny_queue_t *mympd_api_queue;
typedef struct t_work_request {
long conn_id; // needed to identify the connection where to send the reply
char data[1000];
int length;
enum mympd_cmd_ids cmd_id;
} t_work_request;
typedef struct t_work_result {
long conn_id; // needed to identify the connection where to send the reply
char data[MAX_SIZE];
int length;
} t_work_result;
//myMPD configuration
typedef struct t_config {
long mpdport;
char *mpdhost;
char *mpdpass;
char *webport;
bool ssl;
char *sslport;
char *sslcert;
char *sslkey;
char *user;
bool coverimage;
char *coverimagename;
long coverimagesize;
bool stickers;
bool mixramp;
char *taglist;
char *searchtaglist;
char *browsetaglist;
bool smartpls;
char *varlibdir;
char *etcdir;
unsigned long max_elements_per_page;
bool syscmds;
bool localplayer;
long streamport;
char *streamurl;
unsigned long last_played_count;
long loglevel;
char *backgroundcolor;
} t_config;
//global functions
bool testdir(const char *name, const char *dirname);
int randrange(int n);
bool validate_string(const char *data);
int copy_string(char * const dest, char const * const src, size_t const dst_len, size_t const src_len);
int replacechar(char *str, const char orig, const char rep);
enum mympd_cmd_ids get_cmd_id(const char *cmd);
#endif

View File

@ -1,5 +1,5 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
@ -81,7 +81,7 @@ int list_shuffle(struct list *l) {
if (l->length < 2)
return 1;
srand((unsigned int)time(NULL));
// srand((unsigned int)time(NULL));
struct node *current = l->list;
while (current != NULL) {

View File

@ -1,5 +1,5 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
@ -18,19 +18,20 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __LIST_H__
#define __LIST_H__
struct node {
char *data;
long value;
struct node *next;
};
struct list {
unsigned length;
struct node *list;
};
int list_init(struct list *l);
int list_push(struct list *l, const char *data, int value);
int list_insert(struct list *l, const char *data, int value);
@ -43,3 +44,4 @@ int list_shuffle(struct list *l);
int list_sort_by_value(struct list *l, bool order);
int list_swap_item(struct node *n1, struct node *n2);
struct node *list_node_at(const struct list * l, unsigned index);
#endif

374
src/main.c Normal file
View File

@ -0,0 +1,374 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of: ympd (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
ympd project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <pthread.h>
#include <dirent.h>
#include <mpd/client.h>
#include <stdbool.h>
#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "mpd_client.h"
#include "../dist/src/mongoose/mongoose.h"
#include "web_server.h"
#include "mympd_api.h"
#include "../dist/src/inih/ini.h"
static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler
s_signal_received = sig_num;
//Wakeup mympd_api_loop
pthread_cond_signal(&mympd_api_queue->wakeup);
}
static int inihandler(void *user, const char *section, const char *name, const char* value) {
t_config* p_config = (t_config*)user;
char *crap;
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
if (MATCH("mpd", "mpdhost")) {
free(p_config->mpdhost);
p_config->mpdhost = strdup(value);
}
else if (MATCH("mpd", "mpdport")) {
p_config->mpdport = strtol(value, &crap, 10);
}
else if (MATCH("mpd", "mpdpass")) {
free(p_config->mpdpass);
p_config->mpdpass = strdup(value);
}
else if (MATCH("mpd", "streamport")) {
p_config->streamport = strtol(value, &crap, 10);
}
else if (MATCH("webserver", "webport")) {
free(p_config->webport);
p_config->webport = strdup(value);
}
else if (MATCH("webserver", "ssl")) {
p_config->ssl = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("webserver", "sslport")) {
free(p_config->sslport);
p_config->sslport = strdup(value);
}
else if (MATCH("webserver", "sslcert")) {
free(p_config->sslcert);
p_config->sslcert = strdup(value);
}
else if (MATCH("webserver", "sslkey")) {
free(p_config->sslkey);
p_config->sslkey = strdup(value);
}
else if (MATCH("mympd", "user")) {
free(p_config->user);
p_config->user = strdup(value);
}
else if (MATCH("mympd", "coverimage")) {
p_config->coverimage = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "coverimagename")) {
free(p_config->coverimagename);
p_config->coverimagename = strdup(value);
}
else if (MATCH("mympd", "coverimagesize")) {
p_config->coverimagesize = strtol(value, &crap, 10);
}
else if (MATCH("mympd", "varlibdir")) {
free(p_config->varlibdir);
p_config->varlibdir = strdup(value);
}
else if (MATCH("mympd", "stickers")) {
p_config->stickers = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "smartpls")) {
p_config->smartpls = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "mixramp")) {
p_config->mixramp = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "taglist")) {
free(p_config->taglist);
p_config->taglist = strdup(value);
}
else if (MATCH("mympd", "searchtaglist")) {
free(p_config->searchtaglist);
p_config->searchtaglist = strdup(value);
}
else if (MATCH("mympd", "browsetaglist")) {
free(p_config->browsetaglist);
p_config->browsetaglist = strdup(value);
}
else if (MATCH("mympd", "max_elements_per_page")) {
p_config->max_elements_per_page = strtol(value, &crap, 10);
if (p_config->max_elements_per_page > MAX_ELEMENTS_PER_PAGE) {
printf("Setting max_elements_per_page to maximal value %d", MAX_ELEMENTS_PER_PAGE);
p_config->max_elements_per_page = MAX_ELEMENTS_PER_PAGE;
}
}
else if (MATCH("mympd", "syscmds")) {
p_config->syscmds = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "localplayer")) {
p_config->localplayer = strcmp(value, "true") == 0 ? true : false;
}
else if (MATCH("mympd", "streamurl")) {
free(p_config->streamurl);
p_config->streamurl = strdup(value);
}
else if (MATCH("mympd", "last_played_count")) {
p_config->last_played_count = strtol(value, &crap, 10);
}
else if (MATCH("mympd", "loglevel")) {
p_config->loglevel = strtol(value, &crap, 10);
}
else if (MATCH("theme", "backgroundcolor")) {
free(p_config->backgroundcolor);
p_config->backgroundcolor = strdup(value);
}
else {
printf("Unkown config option: %s - %s\n", section, name);
return 0;
}
return 1;
}
static void free_config(t_config *config) {
free(config->mpdhost);
free(config->mpdpass);
free(config->webport);
free(config->sslport);
free(config->sslcert);
free(config->sslkey);
free(config->user);
free(config->coverimagename);
free(config->taglist);
free(config->searchtaglist);
free(config->browsetaglist);
free(config->varlibdir);
free(config->etcdir);
free(config->streamurl);
free(config->backgroundcolor);
free(config);
}
int main(int argc, char **argv) {
s_signal_received = 0;
char testdirname[400];
bool init_webserver = false;
bool init_thread_webserver = false;
bool init_thread_mpdclient = false;
bool init_thread_mympdapi = false;
int rc = EXIT_FAILURE;
mpd_client_queue = tiny_queue_create();
mympd_api_queue = tiny_queue_create();
web_server_queue = tiny_queue_create();
srand((unsigned int)time(NULL));
//mympd config defaults
t_config *config = (t_config *)malloc(sizeof(t_config));
config->mpdhost = strdup("127.0.0.1");
config->mpdport = 6600;
config->mpdpass = NULL;
config->webport = strdup("80");
config->ssl = true;
config->sslport = strdup("443");
config->sslcert = strdup("/etc/mympd/ssl/server.pem");
config->sslkey = strdup("/etc/mympd/ssl/server.key");
config->user = strdup("mympd");
config->streamport = 8000;
config->streamurl = strdup("");
config->coverimage = true;
config->coverimagename = strdup("folder.jpg");
config->coverimagesize = 240;
config->varlibdir = strdup("/var/lib/mympd");
config->stickers = true;
config->mixramp = true;
config->taglist = strdup("Artist,Album,AlbumArtist,Title,Track,Genre,Date,Composer,Performer");
config->searchtaglist = strdup("Artist,Album,AlbumArtist,Title,Genre,Composer,Performer");
config->browsetaglist = strdup("Artist,Album,AlbumArtist,Genre,Composer,Performer");
config->smartpls = true;
config->max_elements_per_page = 100;
config->last_played_count = 20;
char *etcdir = strdup(argv[1]);
config->etcdir = strdup(dirname(etcdir));
free(etcdir);
config->syscmds = false;
config->localplayer = true;
config->loglevel = 1;
config->backgroundcolor = strdup("#888");
if (argc == 2) {
printf("Starting myMPD %s\n", MYMPD_VERSION);
printf("Libmpdclient %i.%i.%i\n", LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION);
printf("Parsing config file: %s\n", argv[1]);
if (ini_parse(argv[1], inihandler, config) < 0) {
printf("Can't load config file \"%s\"\n", argv[1]);
goto cleanup;
}
}
else {
printf("myMPD %s\n"
"Copyright (C) 2018-2019 Juergen Mang <mail@jcgames.de>\n"
"https://github.com/jcorporation/myMPD\n"
"Built " __DATE__ " "__TIME__"\n\n"
"Usage: %s /path/to/mympd.conf\n",
MYMPD_VERSION,
argv[0]
);
goto cleanup;
}
#ifdef DEBUG
printf("Debug flag enabled, setting loglevel to debug\n");
config->loglevel = 3;
#endif
printf("Setting loglevel to %ld\n", config->loglevel);
loglevel = config->loglevel;
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
//init webserver
struct mg_mgr mgr;
if (!web_server_init(&mgr, config)) {
goto cleanup;
}
else {
init_webserver = true;
}
//drop privileges
if (config->user != NULL) {
printf("Droping privileges to %s\n", config->user);
struct passwd *pw;
if ((pw = getpwnam(config->user)) == NULL) {
printf("ERROR: getpwnam() failed, unknown user\n");
goto cleanup;
} else if (setgroups(0, NULL) != 0) {
printf("ERROR: setgroups() failed\n");
goto cleanup;
} else if (setgid(pw->pw_gid) != 0) {
printf("ERROR: setgid() failed\n");
goto cleanup;
} else if (setuid(pw->pw_uid) != 0) {
printf("ERROR: setuid() failed\n");
goto cleanup;
}
}
if (getuid() == 0) {
printf("myMPD should not be run with root privileges\n");
goto cleanup;
}
//check needed directories
if (!testdir("Document root", DOC_ROOT)) {
goto cleanup;
}
snprintf(testdirname, 400, "%s/library", DOC_ROOT);
if (!testdir("Link to mpd music_directory", testdirname)) {
printf("Disabling coverimage support\n");
config->coverimage = false;
}
snprintf(testdirname, 400, "%s/tmp", config->varlibdir);
if (!testdir("Temp dir", testdirname)) {
goto cleanup;
}
snprintf(testdirname, 400, "%s/smartpls", config->varlibdir);
if (!testdir("Smartpls dir", testdirname)) {
goto cleanup;
}
snprintf(testdirname, 400, "%s/state", config->varlibdir);
if (!testdir("State dir", testdirname)) {
goto cleanup;
}
//Create working threads
pthread_t mpd_client_thread, web_server_thread, mympd_api_thread;
//mpd connection
if (pthread_create(&mpd_client_thread, NULL, mpd_client_loop, config) == 0) {
pthread_setname_np(mpd_client_thread, "mympd_mpdclient");
init_thread_mpdclient = true;
}
else {
printf("ERROR: can't create mympd_client thread\n");
s_signal_received = SIGTERM;
}
//webserver
if (pthread_create(&web_server_thread, NULL, web_server_loop, &mgr) == 0) {
pthread_setname_np(web_server_thread, "mympd_webserver");
init_thread_webserver = true;
}
else {
printf("ERROR: can't create mympd_webserver thread\n");
s_signal_received = SIGTERM;
}
//mympd api
if (pthread_create(&mympd_api_thread, NULL, mympd_api_loop, config) == 0) {
pthread_setname_np(mympd_api_thread, "mympd_mympdapi");
init_thread_mympdapi = true;
}
else {
printf("ERROR: can't create mympd_mympdapi thread\n");
s_signal_received = SIGTERM;
}
//Outsourced all work to separate threads, do nothing...
rc = EXIT_SUCCESS;
//Try to cleanup all
cleanup:
if (init_thread_mpdclient)
pthread_join(mpd_client_thread, NULL);
if (init_thread_webserver)
pthread_join(web_server_thread, NULL);
if (init_thread_mympdapi)
pthread_join(mympd_api_thread, NULL);
if (init_webserver)
web_server_free(&mgr);
tiny_queue_free(web_server_queue);
tiny_queue_free(mpd_client_queue);
tiny_queue_free(mympd_api_queue);
free_config(config);
return rc;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
@ -24,265 +24,6 @@
#ifndef __MPD_CLIENT_H__
#define __MPD_CLIENT_H__
void *mpd_client_loop(void *arg_config);
#include "../dist/src/mongoose/mongoose.h"
#include "list.h"
#define RETURN_ERROR_AND_RECOVER(X) do { \
printf("MPD %s: %s\n", X, mpd_connection_get_error_message(mpd.conn)); \
len = json_printf(&out, "{type: error, data: %Q}", mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
return len; \
} while (0)
#define LOG_ERROR_AND_RECOVER(X) do { \
printf("MPD %s: %s\n", X, mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
} while (0)
#define CHECK_RETURN_LEN() do { \
if (len > MAX_SIZE) \
printf("Buffer truncated: %d, %d\n", len, MAX_SIZE); \
return len; \
} while (0)
#define PUT_SONG_TAGS() do { \
struct node *current = mympd_tags.list; \
int tagnr = 0; \
while (current != NULL) { \
if (tagnr ++) \
len += json_printf(&out, ","); \
len += json_printf(&out, "%Q: %Q", current->data, mympd_get_tag(song, mpd_tag_name_parse(current->data))); \
current = current->next; \
} \
len += json_printf(&out, ", Duration: %d, uri: %Q", mpd_song_get_duration(song), mpd_song_get_uri(song)); \
} while (0)
#define PUT_MIN_SONG_TAGS() do { \
len += json_printf(&out, "Title: %Q, Duration: %d, uri: %Q", mympd_get_tag(song, MPD_TAG_TITLE), mpd_song_get_duration(song), mpd_song_get_uri(song)); \
} while (0)
#define LOG_INFO() if (config.loglevel >= 1)
#define LOG_VERBOSE() if (config.loglevel >= 2)
#define LOG_DEBUG() if (config.loglevel == 3)
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
#define GEN_ENUM(X) X,
#define GEN_STR(X) #X,
#define MPD_CMDS(X) \
X(MPD_API_QUEUE_CLEAR) \
X(MPD_API_QUEUE_CROP) \
X(MPD_API_QUEUE_SAVE) \
X(MPD_API_QUEUE_LIST) \
X(MPD_API_QUEUE_SEARCH) \
X(MPD_API_QUEUE_RM_TRACK) \
X(MPD_API_QUEUE_RM_RANGE) \
X(MPD_API_QUEUE_MOVE_TRACK) \
X(MPD_API_QUEUE_ADD_TRACK_AFTER) \
X(MPD_API_QUEUE_ADD_TRACK) \
X(MPD_API_QUEUE_ADD_PLAY_TRACK) \
X(MPD_API_QUEUE_REPLACE_TRACK) \
X(MPD_API_QUEUE_ADD_PLAYLIST) \
X(MPD_API_QUEUE_REPLACE_PLAYLIST) \
X(MPD_API_QUEUE_SHUFFLE) \
X(MPD_API_QUEUE_LAST_PLAYED) \
X(MPD_API_PLAYLIST_CLEAR) \
X(MPD_API_PLAYLIST_RENAME) \
X(MPD_API_PLAYLIST_MOVE_TRACK) \
X(MPD_API_PLAYLIST_ADD_TRACK) \
X(MPD_API_PLAYLIST_RM_TRACK) \
X(MPD_API_PLAYLIST_RM) \
X(MPD_API_PLAYLIST_LIST) \
X(MPD_API_PLAYLIST_CONTENT_LIST) \
X(MPD_API_SMARTPLS_UPDATE_ALL) \
X(MPD_API_SMARTPLS_SAVE) \
X(MPD_API_SMARTPLS_GET) \
X(MPD_API_DATABASE_SEARCH_ADV) \
X(MPD_API_DATABASE_SEARCH) \
X(MPD_API_DATABASE_UPDATE) \
X(MPD_API_DATABASE_RESCAN) \
X(MPD_API_DATABASE_FILESYSTEM_LIST) \
X(MPD_API_DATABASE_TAG_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_LIST) \
X(MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST) \
X(MPD_API_DATABASE_STATS) \
X(MPD_API_DATABASE_SONGDETAILS) \
X(MPD_API_PLAYER_PLAY_TRACK) \
X(MPD_API_PLAYER_VOLUME_SET) \
X(MPD_API_PLAYER_VOLUME_GET) \
X(MPD_API_PLAYER_PAUSE) \
X(MPD_API_PLAYER_PLAY) \
X(MPD_API_PLAYER_STOP) \
X(MPD_API_PLAYER_SEEK) \
X(MPD_API_PLAYER_NEXT) \
X(MPD_API_PLAYER_PREV) \
X(MPD_API_PLAYER_OUTPUT_LIST) \
X(MPD_API_PLAYER_TOGGLE_OUTPUT) \
X(MPD_API_PLAYER_CURRENT_SONG) \
X(MPD_API_PLAYER_STATE) \
X(MPD_API_SETTINGS_GET) \
X(MPD_API_SETTINGS_SET) \
X(MPD_API_WELCOME) \
X(MPD_API_LIKE) \
X(MPD_API_SYSCMD) \
X(MPD_API_COLS_SAVE) \
X(MPD_API_UNKNOWN)
enum mpd_cmd_ids {
MPD_CMDS(GEN_ENUM)
};
enum mpd_conn_states {
MPD_DISCONNECTED,
MPD_FAILURE,
MPD_CONNECTED,
MPD_RECONNECT,
MPD_DISCONNECT
};
struct t_mpd {
// Connection
struct mpd_connection *conn;
enum mpd_conn_states conn_state;
int timeout;
// Reponse Buffer
char buf[MAX_SIZE];
size_t buf_size;
// States
int song_id;
int next_song_id;
int last_song_id;
unsigned queue_version;
unsigned queue_length;
int last_update_sticker_song_id;
int last_last_played_id;
// Features
const unsigned* protocol;
// Supported tags
bool feat_sticker;
bool feat_playlists;
bool feat_tags;
bool feat_library;
bool feat_advsearch;
} mpd;
struct list mpd_tags;
struct list mympd_tags;
struct list mympd_searchtags;
struct list mympd_browsetags;
struct list last_played;
struct list syscmds;
typedef struct {
long mpdport;
const char *mpdhost;
const char *mpdpass;
const char *webport;
bool ssl;
const char *sslport;
const char *sslcert;
const char *sslkey;
const char *user;
bool coverimage;
const char *coverimagename;
long coverimagesize;
bool stickers;
bool mixramp;
const char *taglist;
const char *searchtaglist;
const char *browsetaglist;
bool smartpls;
const char *varlibdir;
const char *etcdir;
unsigned long max_elements_per_page;
bool syscmds;
bool localplayer;
long streamport;
const char *streamurl;
unsigned long last_played_count;
long loglevel;
} t_config;
t_config config;
typedef struct {
long playCount;
long skipCount;
long lastPlayed;
long like;
} t_sticker;
typedef struct {
bool notificationWeb;
bool notificationPage;
int jukeboxMode;
const char *jukeboxPlaylist;
int jukeboxQueueLength;
char *colsQueueCurrent;
char *colsSearch;
char *colsBrowseDatabase;
char *colsBrowsePlaylistsDetail;
char *colsBrowseFilesystem;
char *colsPlayback;
char *colsQueueLastPlayed;
} t_mympd_state;
t_mympd_state mympd_state;
static int is_websocket(const struct mg_connection *nc) {
return nc->flags & MG_F_IS_WEBSOCKET;
}
int randrange(int n);
void mympd_idle(struct mg_mgr *sm, int timeout);
void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask);
void callback_mympd(struct mg_connection *nc, const struct mg_str msg);
void mympd_notify(struct mg_mgr *s);
bool mympd_count_song_id(int song_id, char *name, int value);
bool mympd_count_song_uri(const char *uri, char *name, int value);
bool mympd_like_song_uri(const char *uri, int value);
bool mympd_last_played_song_uri(const char *uri);
bool mympd_last_played_song_id(int song_id);
bool mympd_get_sticker(const char *uri, t_sticker *sticker);
bool mympd_last_played_list(int song_id);
bool mympd_jukebox();
bool mympd_state_get(char *name, char *value);
bool mympd_state_set(const char *name, const char *value);
int mympd_syscmd(char *buffer, char *cmd, int order);
int mympd_smartpls_save(char *smartpltype, char *playlist, char *tag, char *searchstr, int maxentries, int timerange);
int mympd_smartpls_put(char *buffer, char *playlist);
int mympd_smartpls_update_all();
int mympd_smartpls_clear(char *playlist);
int mympd_smartpls_update_sticker(char *playlist, char *sticker, int maxentries);
int mympd_smartpls_update_newest(char *playlist, int timerange);
int mympd_smartpls_update_search(char *playlist, char *tag, char *searchstr);
int mympd_get_updatedb_state(char *buffer);
int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, int *last_song_id, unsigned *queue_version, unsigned *queue_length);
int mympd_put_outputs(char *buffer);
int mympd_put_current_song(char *buffer);
int mympd_put_queue(char *buffer, unsigned int offset, unsigned *queue_version, unsigned *queue_length);
int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter);
int mympd_search(char *buffer, char *searchstr, char *filter, char *plist, unsigned int offset);
int mympd_search_adv(char *buffer, char *expression, char *sort, bool sortdesc, char *grouptag, char *plist, unsigned int offset);
int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr);
int mympd_put_welcome(char *buffer);
int mympd_put_volume(char *buffer);
int mympd_put_stats(char *buffer);
int mympd_put_settings(char *buffer);
int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *mpdsearchtagtype, char *searchstr, char *filter);
int mympd_put_songs_in_album(char *buffer, char *album, char *search, char *tag);
int mympd_put_playlists(char *buffer, unsigned int offset, char *filter);
int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char *filter);
int mympd_put_songdetails(char *buffer, char *uri);
int mympd_put_last_played_songs(char *buffer, unsigned int offset);
int mympd_queue_crop(char *buffer);
void mympd_disconnect();
#endif

View File

@ -1,564 +0,0 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <mpd/client.h>
#include "../dist/src/mongoose/mongoose.h"
#include "../dist/src/frozen/frozen.h"
#include "../dist/src/inih/ini.h"
#include "mpd_client.h"
#include "config.h"
static sig_atomic_t s_signal_received = 0;
static struct mg_serve_http_opts s_http_server_opts;
static void signal_handler(int sig_num) {
signal(sig_num, signal_handler); // Reinstantiate signal handler
s_signal_received = sig_num;
}
static void handle_api(struct mg_connection *nc, struct http_message *hm) {
if (!is_websocket(nc))
mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n\r\n");
char buf[1000] = {0};
int len = sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len;
memcpy(buf, hm->body.p, len);
struct mg_str d = {buf, len};
callback_mympd(nc, d);
if (!is_websocket(nc))
mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
}
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch(ev) {
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("New websocket request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
LOG_VERBOSE() printf("New Websocket connection established\n");
struct mg_str d = mg_mk_str("{\"cmd\":\"MPD_API_WELCOME\"}");
callback_mympd(nc, d);
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("HTTP request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0)
handle_api(nc, hm);
else
mg_serve_http(nc, hm, s_http_server_opts);
break;
}
case MG_EV_CLOSE: {
if (is_websocket(nc)) {
LOG_VERBOSE() printf("Websocket connection closed\n");
}
else {
LOG_VERBOSE() printf("HTTP connection closed\n");
}
break;
}
}
}
static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data) {
char *host;
char host_header[1024];
switch(ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
snprintf(host_header, 1024, "%.*s", host_hdr->len, host_hdr->p);
host = strtok(host_header, ":");
char s_redirect[250];
if (strcmp(config.sslport, "443") == 0)
snprintf(s_redirect, 250, "https://%s/", host);
else
snprintf(s_redirect, 250, "https://%s:%s/", host, config.sslport);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
}
}
static int inihandler(void* user, const char* section, const char* name, const char* value) {
t_config* p_config = (t_config*)user;
char *crap;
#define MATCH(n) strcmp(name, n) == 0
if (MATCH("mpdhost"))
p_config->mpdhost = strdup(value);
else if (MATCH("mpdport"))
p_config->mpdport = strtol(value, &crap, 10);
else if (MATCH("mpdpass"))
p_config->mpdpass = strdup(value);
else if (MATCH("webport"))
p_config->webport = strdup(value);
else if (MATCH("ssl"))
if (strcmp(value, "true") == 0)
p_config->ssl = true;
else
p_config->ssl = false;
else if (MATCH("sslport"))
p_config->sslport = strdup(value);
else if (MATCH("sslcert"))
p_config->sslcert = strdup(value);
else if (MATCH("sslkey"))
p_config->sslkey = strdup(value);
else if (MATCH("user"))
p_config->user = strdup(value);
else if (MATCH("streamport"))
p_config->streamport = strtol(value, &crap, 10);
else if (MATCH("coverimage"))
if (strcmp(value, "true") == 0)
p_config->coverimage = true;
else
p_config->coverimage = false;
else if (MATCH("coverimagename"))
p_config->coverimagename = strdup(value);
else if (MATCH("coverimagesize"))
p_config->coverimagesize = strtol(value, &crap, 10);
else if (MATCH("varlibdir"))
p_config->varlibdir = strdup(value);
else if (MATCH("stickers"))
if (strcmp(value, "true") == 0)
p_config->stickers = true;
else
p_config->stickers = false;
else if (MATCH("smartpls"))
if (strcmp(value, "true") == 0)
p_config->smartpls = true;
else
p_config->smartpls = false;
else if (MATCH("mixramp"))
if (strcmp(value, "true") == 0)
p_config->mixramp = true;
else
p_config->mixramp = false;
else if (MATCH("taglist"))
p_config->taglist = strdup(value);
else if (MATCH("searchtaglist"))
p_config->searchtaglist = strdup(value);
else if (MATCH("browsetaglist"))
p_config->browsetaglist = strdup(value);
else if (MATCH("max_elements_per_page")) {
p_config->max_elements_per_page = strtol(value, &crap, 10);
if (p_config->max_elements_per_page > MAX_ELEMENTS_PER_PAGE) {
printf("Setting max_elements_per_page to maximal value %d", MAX_ELEMENTS_PER_PAGE);
p_config->max_elements_per_page = MAX_ELEMENTS_PER_PAGE;
}
}
else if (MATCH("syscmds"))
if (strcmp(value, "true") == 0)
p_config->syscmds = true;
else
p_config->syscmds = false;
else if (MATCH("localplayer"))
if (strcmp(value, "true") == 0)
p_config->localplayer = true;
else
p_config->localplayer = false;
else if (MATCH("streamurl"))
p_config->streamurl = strdup(value);
else if (MATCH("last_played_count"))
p_config->last_played_count = strtol(value, &crap, 10);
else if (MATCH("loglevel"))
p_config->loglevel = strtol(value, &crap, 10);
else {
printf("Unkown config line: %s\n", name);
return 0; /* unknown section/name, error */
}
return 1;
}
void read_syscmds() {
DIR *dir;
struct dirent *ent;
char dirname[400];
char *cmd;
long order;
if (config.syscmds == true) {
snprintf(dirname, 400, "%s/syscmds", config.etcdir);
LOG_INFO() printf("Reading syscmds: %s\n", dirname);
if ((dir = opendir (dirname)) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, ".", 1) == 0)
continue;
order = strtol(ent->d_name, &cmd, 10);
if (strcmp(cmd, "") != 0)
list_push(&syscmds, strdup(cmd), order);
}
closedir(dir);
}
}
else
LOG_INFO() printf("Syscmds are disabled\n");
}
void read_statefiles() {
char *crap;
char value[400];
LOG_INFO() printf("Reading states\n");
if (mympd_state_get("notificationWeb", value)) {
if (strcmp(value, "true") == 0)
mympd_state.notificationWeb = true;
else
mympd_state.notificationWeb = false;
}
else {
mympd_state.notificationWeb = false;
mympd_state_set("notificationWeb", "false");
}
if (mympd_state_get("notificationPage", value)) {
if (strcmp(value, "true") == 0)
mympd_state.notificationPage = true;
else
mympd_state.notificationPage = false;
}
else {
mympd_state.notificationPage = true;
mympd_state_set("notificationPage", "true");
}
if (mympd_state_get("jukeboxMode", value))
mympd_state.jukeboxMode = strtol(value, &crap, 10);
else {
mympd_state.jukeboxMode = 0;
mympd_state_set("jukeboxMode", "0");
}
if (mympd_state_get("jukeboxPlaylist", value))
mympd_state.jukeboxPlaylist = strdup(value);
else {
mympd_state.jukeboxPlaylist = strdup("Database");
mympd_state_set("jukeboxPlaylist", "Database");
}
if (mympd_state_get("jukeboxQueueLength", value))
mympd_state.jukeboxQueueLength = strtol(value, &crap, 10);
else {
mympd_state.jukeboxQueueLength = 1;
mympd_state_set("jukeboxQueueLength", "1");
}
if (mympd_state_get("colsQueueCurrent", value))
mympd_state.colsQueueCurrent = strdup(value);
else {
mympd_state.colsQueueCurrent = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsQueueCurrent", mympd_state.colsQueueCurrent);
}
if (mympd_state_get("colsSearch", value))
mympd_state.colsSearch = strdup(value);
else {
mympd_state.colsSearch = strdup("[\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsSearch", mympd_state.colsSearch);
}
if (mympd_state_get("colsBrowseDatabase", value))
mympd_state.colsBrowseDatabase = strdup(value);
else {
mympd_state.colsBrowseDatabase = strdup("[\"Track\",\"Title\",\"Duration\"]");
mympd_state_set("colsBrowseDatabase", mympd_state.colsBrowseDatabase);
}
if (mympd_state_get("colsBrowsePlaylistsDetail", value))
mympd_state.colsBrowsePlaylistsDetail = strdup(value);
else {
mympd_state.colsBrowsePlaylistsDetail = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsBrowsePlaylistsDetail", mympd_state.colsBrowsePlaylistsDetail);
}
if (mympd_state_get("colsBrowseFilesystem", value))
mympd_state.colsBrowseFilesystem = strdup(value);
else {
mympd_state.colsBrowseFilesystem = strdup("[\"Type\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state_set("colsBrowseFilesystem", mympd_state.colsBrowseFilesystem);
}
if (mympd_state_get("colsPlayback", value))
mympd_state.colsPlayback = strdup(value);
else {
mympd_state.colsPlayback = strdup("[\"Artist\",\"Album\",\"Genre\"]");
mympd_state_set("colsPlayback", mympd_state.colsPlayback);
}
if (mympd_state_get("colsQueueLastPlayed", value))
mympd_state.colsQueueLastPlayed = strdup(value);
else {
mympd_state.colsQueueLastPlayed = strdup("[\"Pos\",\"Title\",\"Artist\",\"Album\",\"LastPlayed\"]");
mympd_state_set("colsQueueLastPlayed", mympd_state.colsQueueLastPlayed);
}
}
int read_last_played() {
char cfgfile[400];
char *line;
char *data;
size_t n = 0;
ssize_t read;
long value;
snprintf(cfgfile, 400, "%s/state/last_played", config.varlibdir);
FILE *fp = fopen(cfgfile, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfgfile);
return 0;
}
while ((read = getline(&line, &n, fp)) > 0) {
value = strtol(line, &data, 10);
if (strlen(data) > 2)
data = data + 2;
strtok(data, "\n");
list_push(&last_played, data, value);
}
fclose(fp);
return last_played.length;;
}
bool testdir(char *name, char *dirname) {
DIR* dir = opendir(dirname);
if (dir) {
closedir(dir);
LOG_INFO() printf("%s: \"%s\"\n", name, dirname);
return true;
}
else {
printf("%s: \"%s\" don't exists\n", name, dirname);
return false;
}
}
int main(int argc, char **argv) {
struct mg_mgr mgr;
struct mg_connection *nc;
struct mg_connection *nc_http;
struct mg_bind_opts bind_opts;
const char *err;
char testdirname[400];
//defaults
config.mpdhost = "127.0.0.1";
config.mpdport = 6600;
config.mpdpass = NULL;
config.webport = "80";
config.ssl = true;
config.sslport = "443";
config.sslcert = "/etc/mympd/ssl/server.pem";
config.sslkey = "/etc/mympd/ssl/server.key";
config.user = "mympd";
config.streamport = 8000;
config.streamurl = "";
config.coverimage = true;
config.coverimagename = "folder.jpg";
config.coverimagesize = 240;
config.varlibdir = "/var/lib/mympd";
config.stickers = true;
config.mixramp = true;
config.taglist = "Artist,Album,AlbumArtist,Title,Track,Genre,Date,Composer,Performer";
config.searchtaglist = "Artist,Album,AlbumArtist,Title,Genre,Composer,Performer";
config.browsetaglist = "Artist,Album,AlbumArtist,Genre,Composer,Performer";
config.smartpls = true;
config.max_elements_per_page = 100;
config.last_played_count = 20;
char *etcdir = strdup(argv[1]);
config.etcdir = dirname(etcdir);
config.syscmds = false;
config.localplayer = true;
config.loglevel = 1;
mpd.timeout = 3000;
mpd.last_update_sticker_song_id = -1;
mpd.last_song_id = -1;
mpd.last_last_played_id = -1;
mpd.feat_library = false;
if (argc == 2) {
LOG_INFO() printf("Starting myMPD %s\n", MYMPD_VERSION);
LOG_INFO() printf("Libmpdclient %i.%i.%i\n", LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION);
LOG_INFO() printf("Parsing config file: %s\n", argv[1]);
if (ini_parse(argv[1], inihandler, &config) < 0) {
printf("Can't load config file \"%s\"\n", argv[1]);
return EXIT_FAILURE;
}
}
else {
printf("myMPD %s\n"
"Copyright (C) 2018 Juergen Mang <mail@jcgames.de>\n"
"https://github.com/jcorporation/myMPD\n"
"Built " __DATE__ " "__TIME__"\n\n"
"Usage: %s /path/to/mympd.conf\n",
MYMPD_VERSION,
argv[0]
);
return EXIT_FAILURE;
}
#ifdef DEBUG
printf("Debug flag enabled, setting loglevel to debug\n");
config.loglevel = 3;
#endif
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
mg_mgr_init(&mgr, NULL);
if (config.ssl == true) {
nc_http = mg_bind(&mgr, config.webport, ev_handler_http);
if (nc_http == NULL) {
printf("Error listening on port %s\n", config.webport);
return EXIT_FAILURE;
}
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = config.sslcert;
bind_opts.ssl_key = config.sslkey;
bind_opts.error_string = &err;
nc = mg_bind_opt(&mgr, config.sslport, ev_handler, bind_opts);
if (nc == NULL) {
printf("Error listening on port %s: %s\n", config.sslport, err);
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
}
else {
nc = mg_bind(&mgr, config.webport, ev_handler);
if (nc == NULL) {
printf("Error listening on port %s\n", config.webport);
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
}
if (config.user != NULL) {
LOG_INFO() printf("Droping privileges to %s\n", config.user);
struct passwd *pw;
if ((pw = getpwnam(config.user)) == NULL) {
printf("getpwnam() failed, unknown user\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setgroups(0, NULL) != 0) {
printf("setgroups() failed\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setgid(pw->pw_gid) != 0) {
printf("setgid() failed\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setuid(pw->pw_uid) != 0) {
printf("setuid() failed\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
}
if (getuid() == 0) {
printf("myMPD should not be run with root privileges\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
}
if (!testdir("Document root", SRC_PATH))
return EXIT_FAILURE;
snprintf(testdirname, 400, "%s/library", SRC_PATH);
if (testdir("Link to mpd music_directory", testdirname)) {
LOG_INFO() printf("Enabling featLibrary support\n");
mpd.feat_library = true;
}
else {
LOG_INFO() printf("Disabling coverimage support\n");
config.coverimage = false;
}
snprintf(testdirname, 400, "%s/tmp", config.varlibdir);
if (!testdir("Temp dir", testdirname))
return EXIT_FAILURE;
snprintf(testdirname, 400, "%s/smartpls", config.varlibdir);
if (!testdir("Smartpls dir", testdirname))
return EXIT_FAILURE;
snprintf(testdirname, 400, "%s/state", config.varlibdir);
if (!testdir("State dir", testdirname))
return EXIT_FAILURE;
read_statefiles();
list_init(&syscmds);
read_syscmds();
list_sort_by_value(&syscmds, true);
list_init(&mpd_tags);
list_init(&mympd_tags);
list_init(&last_played);
LOG_INFO() printf("Reading last played songs: %d\n", read_last_played());
if (config.ssl == true)
mg_set_protocol_http_websocket(nc_http);
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = SRC_PATH;
s_http_server_opts.enable_directory_listing = "no";
LOG_INFO() printf("Listening on http port %s\n", config.webport);
if (config.ssl == true)
LOG_INFO() printf("Listening on ssl port %s\n", config.sslport);
while (s_signal_received == 0) {
mg_mgr_poll(&mgr, 100);
mympd_idle(&mgr, 0);
}
mg_mgr_free(&mgr);
list_free(&mpd_tags);
list_free(&mympd_tags);
mympd_disconnect();
return EXIT_SUCCESS;
}

520
src/mympd_api.c Normal file
View File

@ -0,0 +1,520 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <libgen.h>
#include <dirent.h>
#include <stdbool.h>
#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "mympd_api.h"
#include "mpd_client.h"
#include "../dist/src/frozen/frozen.h"
//private definitions
typedef struct t_mympd_state {
//notifications
bool notificationWeb;
bool notificationPage;
//jukebox
enum jukebox_modes jukeboxMode;
char *jukeboxPlaylist;
int jukeboxQueueLength;
bool autoPlay;
//columns
char *colsQueueCurrent;
char *colsSearch;
char *colsBrowseDatabase;
char *colsBrowsePlaylistsDetail;
char *colsBrowseFilesystem;
char *colsPlayback;
char *colsQueueLastPlayed;
//system commands
struct list syscmd_list;
} t_mympd_state;
static void mympd_api(t_config *config, t_mympd_state *mympd_state, t_work_request *request);
static bool mympd_api_read_syscmds(t_config *config, t_mympd_state *mympd_state);
static int mympd_api_syscmd(t_config *config, t_mympd_state *mympd_state, char *buffer, const char *cmd);
static void mympd_api_read_statefiles(t_config *config, t_mympd_state *mympd_state);
static char *state_file_rw_string(t_config *config, const char *name, const char *def_value);
static bool state_file_rw_bool(t_config *config, const char *name, const bool def_value);
static long state_file_rw_long(t_config *config, const char *name, const long def_value);
static bool state_file_write(t_config *config, const char *name, const char *value);
static int mympd_api_put_settings(t_config *config, t_mympd_state *mympd_state, char *buffer);
//public functions
void *mympd_api_loop(void *arg_config) {
t_config *config = (t_config *) arg_config;
//read myMPD states under config.varlibdir
t_mympd_state mympd_state;
mympd_api_read_statefiles(config, &mympd_state);
//push jukebox settings to mpd_client queue
t_work_request *mpd_client_request = (t_work_request *)malloc(sizeof(t_work_request));
mpd_client_request->conn_id = 0;
mpd_client_request->cmd_id = MYMPD_API_SETTINGS_SET;
mpd_client_request->length = snprintf(mpd_client_request->data, 1000,
"{\"cmd\":\"MYMPD_API_SETTINGS_SET\", \"data\":{\"jukeboxMode\": %d, \"jukeboxPlaylist\": \"%s\", \"jukeboxQueueLength\": %d}}",
mympd_state.jukeboxMode,
mympd_state.jukeboxPlaylist,
mympd_state.jukeboxQueueLength
);
tiny_queue_push(mpd_client_queue, mpd_client_request);
//read system command files
list_init(&mympd_state.syscmd_list);
bool rc = mympd_api_read_syscmds(config, &mympd_state);
if (rc == true) {
list_sort_by_value(&mympd_state.syscmd_list, true);
}
while (s_signal_received == 0) {
struct t_work_request *request = tiny_queue_shift(mympd_api_queue, 0);
if (request != NULL) {
mympd_api(config, &mympd_state, request);
}
}
list_free(&mympd_state.syscmd_list);
free(mympd_state.jukeboxPlaylist);
free(mympd_state.colsQueueCurrent);
free(mympd_state.colsSearch);
free(mympd_state.colsBrowseDatabase);
free(mympd_state.colsBrowsePlaylistsDetail);
free(mympd_state.colsBrowseFilesystem);
free(mympd_state.colsPlayback);
free(mympd_state.colsQueueLastPlayed);
return NULL;
}
//private functions
static void mympd_api(t_config *config, t_mympd_state *mympd_state, t_work_request *request) {
//t_work_request *request = (t_work_request *) arg_request;
size_t len = 0;
char buffer[MAX_SIZE];
int je;
char *p_charbuf1;
char p_char[4];
LOG_VERBOSE() printf("MYMPD API request: %.*s\n", request->length, request->data);
if (request->cmd_id == MYMPD_API_SYSCMD) {
if (config->syscmds == true) {
je = json_scanf(request->data, request->length, "{data: {cmd: %Q}}", &p_charbuf1);
if (je == 1) {
len = mympd_api_syscmd(config, mympd_state, buffer, p_charbuf1);
free(p_charbuf1);
}
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"System commands are disabled.\"}");
}
}
else if (request->cmd_id == MYMPD_API_COLS_SAVE) {
je = json_scanf(request->data, request->length, "{data: {table: %Q}}", &p_charbuf1);
if (je == 1) {
char column_list[800];
snprintf(column_list, 800, "%.*s", request->length, request->data);
char *cols = strchr(column_list, '[');
int col_len = strlen(cols);
if (col_len > 1)
cols[col_len - 2] = '\0';
if (strcmp(p_charbuf1, "colsQueueCurrent") == 0) {
free(mympd_state->colsQueueCurrent);
mympd_state->colsQueueCurrent = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsSearch") == 0) {
free(mympd_state->colsSearch);
mympd_state->colsSearch = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsBrowseDatabase") == 0) {
free(mympd_state->colsBrowseDatabase);
mympd_state->colsBrowseDatabase = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsBrowsePlaylistsDetail") == 0) {
free(mympd_state->colsBrowsePlaylistsDetail);
mympd_state->colsBrowsePlaylistsDetail = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsBrowseFilesystem") == 0) {
free(mympd_state->colsBrowseFilesystem);
mympd_state->colsBrowseFilesystem = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsPlayback") == 0) {
free(mympd_state->colsPlayback);
mympd_state->colsPlayback = strdup(cols);
}
else if (strcmp(p_charbuf1, "colsQueueLastPlayed") == 0) {
free(mympd_state->colsQueueLastPlayed);
mympd_state->colsQueueLastPlayed = strdup(cols);
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown table %s\"}", p_charbuf1);
printf("MYMPD_API_COLS_SAVE: Unknown table %s\n", p_charbuf1);
}
if (len == 0) {
if (state_file_write(config, p_charbuf1, cols))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
}
free(p_charbuf1);
}
}
else if (request->cmd_id == MYMPD_API_SETTINGS_SET) {
je = json_scanf(request->data, request->length, "{data: {notificationWeb: %B}}", &mympd_state->notificationWeb);
if (je == 1) {
if (!state_file_write(config, "notificationWeb", (mympd_state->notificationWeb == true ? "true" : "false")))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationWeb.\"}");
}
je = json_scanf(request->data, request->length, "{data: {notificationPage: %B}}", &mympd_state->notificationPage);
if (je == 1) {
if (!state_file_write(config, "notificationPage", (mympd_state->notificationPage == true ? "true" : "false")))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state notificationPage.\"}");
}
je = json_scanf(request->data, request->length, "{data: {autoPlay: %B}}", &mympd_state->autoPlay);
if (je == 1) {
if (!state_file_write(config, "autoPlay", (mympd_state->autoPlay == true ? "true" : "false")))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state autoPlay.\"}");
}
je = json_scanf(request->data, request->length, "{data: {jukeboxMode: %d}}", &mympd_state->jukeboxMode);
if (je == 1) {
snprintf(p_char, 4, "%d", mympd_state->jukeboxMode);
if (!state_file_write(config, "jukeboxMode", p_char))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxMode.\"}");
}
je = json_scanf(request->data, request->length, "{data: {jukeboxPlaylist: %Q}}", &p_charbuf1);
if (je == 1) {
free(mympd_state->jukeboxPlaylist);
mympd_state->jukeboxPlaylist = p_charbuf1;
p_charbuf1 = NULL;
if (!state_file_write(config, "jukeboxPlaylist", mympd_state->jukeboxPlaylist))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxPlaylist.\"}");
}
je = json_scanf(request->data, request->length, "{data: {jukeboxQueueLength: %d}}", &mympd_state->jukeboxQueueLength);
if (je == 1) {
snprintf(p_char, 4, "%d", mympd_state->jukeboxQueueLength);
if (!state_file_write(config, "jukeboxQueueLength", p_char))
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxQueueLength.\"}");
}
if (len == 0) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
}
//push settings to mpd_client queue
t_work_request *mpd_client_request = (t_work_request *)malloc(sizeof(t_work_request));
mpd_client_request->conn_id = request->conn_id;
mpd_client_request->cmd_id = request->cmd_id;
mpd_client_request->length = copy_string(mpd_client_request->data, request->data, 1000, request->length);
tiny_queue_push(mpd_client_queue, mpd_client_request);
}
else if (request->cmd_id == MYMPD_API_SETTINGS_GET) {
len = mympd_api_put_settings(config, mympd_state, buffer);
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown cmd_id %u.\"}", request->cmd_id);
printf("ERROR: Unknown cmd_id %u\n", request->cmd_id);
}
if (len == 0) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"No response for cmd_id %u.\"}", request->cmd_id);
printf("ERROR: No response for cmd_id %u\n", request->cmd_id);
}
LOG_DEBUG() fprintf(stderr, "DEBUG: Send http response to connection %lu (first 800 chars):\n%*.*s\n", request->conn_id, 0, 800, buffer);
t_work_result *response = (t_work_result *)malloc(sizeof(t_work_result));
response->conn_id = request->conn_id;
response->length = copy_string(response->data, buffer, MAX_SIZE, len);
tiny_queue_push(web_server_queue, response);
free(request);
}
static bool mympd_api_read_syscmds(t_config *config, t_mympd_state *mympd_state) {
DIR *dir;
struct dirent *ent;
char dirname[400];
char *cmd;
long order;
if (config->syscmds == true) {
snprintf(dirname, 400, "%s/syscmds", config->etcdir);
printf("Reading syscmds: %s\n", dirname);
if ((dir = opendir (dirname)) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, ".", 1) == 0)
continue;
order = strtol(ent->d_name, &cmd, 10);
if (strcmp(cmd, "") != 0) {
list_push(&mympd_state->syscmd_list, cmd, order);
}
else {
printf("ERROR: Can't read syscmd file %s\n", ent->d_name);
}
}
closedir(dir);
}
else {
printf("ERROR: Can't read syscmds\n");
}
}
else {
printf("Syscmds are disabled\n");
}
return true;
}
static int mympd_api_syscmd(t_config *config, t_mympd_state *mympd_state, char *buffer, const char *cmd) {
int len;
char filename[400];
char *line = NULL;
char *crap;
size_t n = 0;
ssize_t read;
const int order = list_get_value(&mympd_state->syscmd_list, cmd);
if (order == -1) {
printf("ERROR: Syscmd not defined: %s\n", cmd);
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"System command not defined\"}");
return len;
}
snprintf(filename, 400, "%s/syscmds/%d%s", config->etcdir, order, cmd);
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't execute cmd %s.\"}", cmd);
printf("ERROR: Can't execute syscmd \"%s\"\n", cmd);
return len;
}
read = getline(&line, &n, fp);
fclose(fp);
if (read > 0) {
strtok_r(line, "\n", &crap);
const int rc = system(line);
if ( rc == 0) {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Executed cmd %s.\"}", cmd);
LOG_VERBOSE() printf("Executed syscmd: \"%s\"\n", line);
}
else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Executing cmd %s failed.\"}", cmd);
printf("ERROR: Executing syscmd \"%s\" failed.\n", cmd);
}
} else {
len = snprintf(buffer, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't execute cmd %s.\"}", cmd);
printf("ERROR: Can't execute syscmd \"%s\"\n", cmd);
}
free(line);
CHECK_RETURN_LEN();
}
static void mympd_api_read_statefiles(t_config *config, t_mympd_state *mympd_state) {
LOG_INFO() printf("Reading states\n");
mympd_state->notificationWeb = state_file_rw_bool(config, "notificationWeb", false);
mympd_state->notificationPage = state_file_rw_bool(config, "notificationPage", true);
mympd_state->autoPlay = state_file_rw_bool(config, "autoPlay", false);
mympd_state->jukeboxMode = state_file_rw_long(config, "jukeboxMode", JUKEBOX_OFF);
mympd_state->jukeboxPlaylist = state_file_rw_string(config, "jukeboxPlaylist", "Database");
mympd_state->jukeboxQueueLength = state_file_rw_long(config, "jukeboxQueueLength", 1);
mympd_state->colsQueueCurrent = state_file_rw_string(config, "colsQueueCurrent", "[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsSearch = state_file_rw_string(config, "colsSearch", "[\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsBrowseDatabase = state_file_rw_string(config, "colsBrowseDatabase", "[\"Track\",\"Title\",\"Duration\"]");
mympd_state->colsBrowsePlaylistsDetail = state_file_rw_string(config, "colsBrowsePlaylistsDetail", "[\"Pos\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsBrowseFilesystem = state_file_rw_string(config, "colsBrowseFilesystem", "[\"Type\",\"Title\",\"Artist\",\"Album\",\"Duration\"]");
mympd_state->colsPlayback = state_file_rw_string(config, "colsPlayback", "[\"Artist\",\"Album\"]");
mympd_state->colsQueueLastPlayed = state_file_rw_string(config, "colsQueueLastPlayed", "[\"Pos\",\"Title\",\"Artist\",\"Album\",\"LastPlayed\"]");
}
static char *state_file_rw_string(t_config *config, const char *name, const char *def_value) {
char cfg_file[400];
char *line = NULL;
size_t n = 0;
ssize_t read;
if (!validate_string(name)) {
return NULL;
}
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
FILE *fp = fopen(cfg_file, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfg_file);
state_file_write(config, name, def_value);
return NULL;
}
read = getline(&line, &n, fp);
if (read > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: State %s: %s\n", name, line);
}
fclose(fp);
if (read > 0) {
return line;
}
else {
free(line);
return NULL;
}
}
static bool state_file_rw_bool(t_config *config, const char *name, const bool def_value) {
char cfg_file[400];
char *line = NULL;
size_t n = 0;
ssize_t read;
bool value = def_value;
if (!validate_string(name))
return def_value;
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
FILE *fp = fopen(cfg_file, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfg_file);
state_file_write(config, name, def_value == true ? "true" : "false");
return def_value;
}
read = getline(&line, &n, fp);
if (read > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: State %s: %s\n", name, line);
value = strcmp(line, "true") == 0 ? true : false;
}
fclose(fp);
free(line);
return value;
}
static long state_file_rw_long(t_config *config, const char *name, const long def_value) {
char cfg_file[400];
char *line = NULL;
char *crap;
size_t n = 0;
ssize_t read;
long value = def_value;
if (!validate_string(name))
return def_value;
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
FILE *fp = fopen(cfg_file, "r");
if (fp == NULL) {
printf("Error opening %s\n", cfg_file);
char p_value[65];
snprintf(p_value, 65, "%ld", def_value);
state_file_write(config, name, p_value);
return def_value;
}
read = getline(&line, &n, fp);
if (read > 0) {
LOG_DEBUG() fprintf(stderr, "DEBUG: State %s: %s\n", name, line);
value = strtol(line, &crap, 10);
}
fclose(fp);
free(line);
return value;
}
static bool state_file_write(t_config *config, const char *name, const char *value) {
char tmp_file[400];
char cfg_file[400];
if (!validate_string(name))
return false;
snprintf(cfg_file, 400, "%s/state/%s", config->varlibdir, name);
snprintf(tmp_file, 400, "%s/tmp/%s", config->varlibdir, name);
FILE *fp = fopen(tmp_file, "w");
if (fp == NULL) {
printf("Error opening %s\n", tmp_file);
return false;
}
fprintf(fp, "%s", value);
fclose(fp);
if (rename(tmp_file, cfg_file) == -1) {
printf("Error renaming file from %s to %s\n", tmp_file, cfg_file);
return false;
}
return true;
}
static int mympd_api_put_settings(t_config *config, t_mympd_state *mympd_state, char *buffer) {
size_t len;
int nr = 0;
struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
len = json_printf(&out, "{type: mympdSettings, data: {mpdhost: %Q, mpdport: %d, passwort_set: %B, featSyscmds: %B, "
"featLocalplayer: %B, streamport: %d, streamurl: %Q, featCoverimage: %B, coverimagename: %Q, coverimagesize: %d, featMixramp: %B, "
"maxElementsPerPage: %d, notificationWeb: %B, notificationPage: %B, jukeboxMode: %d, jukeboxPlaylist: %Q, jukeboxQueueLength: %d, "
"autoPlay: %B, backgroundcolor: %Q",
config->mpdhost,
config->mpdport,
config->mpdpass ? "true" : "false",
config->syscmds,
config->localplayer,
config->streamport,
config->streamurl,
config->coverimage,
config->coverimagename,
config->coverimagesize,
config->mixramp,
config->max_elements_per_page,
mympd_state->notificationWeb,
mympd_state->notificationPage,
mympd_state->jukeboxMode,
mympd_state->jukeboxPlaylist,
mympd_state->jukeboxQueueLength,
mympd_state->autoPlay,
config->backgroundcolor
);
if (config->syscmds == true) {
len += json_printf(&out, ", syscmdList: [");
nr = 0;
struct node *current = mympd_state->syscmd_list.list;
while (current != NULL) {
if (nr++)
len += json_printf(&out, ",");
len += json_printf(&out, "%Q", current->data);
current = current->next;
}
len += json_printf(&out, "]");
}
len += json_printf(&out, ", colsQueueCurrent: %s, colsSearch: %s, colsBrowseDatabase: %s, colsBrowsePlaylistsDetail: %s, "
"colsBrowseFilesystem: %s, colsPlayback: %s, colsQueueLastPlayed: %s}}",
mympd_state->colsQueueCurrent,
mympd_state->colsSearch,
mympd_state->colsBrowseDatabase,
mympd_state->colsBrowsePlaylistsDetail,
mympd_state->colsBrowseFilesystem,
mympd_state->colsPlayback,
mympd_state->colsQueueLastPlayed
);
CHECK_RETURN_LEN();
}

View File

@ -1,5 +1,5 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
@ -22,5 +22,9 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
void sanitize_string(const char *data);
int validate_path(char *path, const char *basepath);
#ifndef __MYMPD_API_H__
#define __MYMPD_API_H__
void *mympd_api_loop(void *arg_config);
#endif

179
src/tiny_queue.c Normal file
View File

@ -0,0 +1,179 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>
#include <asm/errno.h>
#include "tiny_queue.h"
tiny_queue_t *tiny_queue_create(void) {
struct tiny_queue_t* queue = (struct tiny_queue_t*)malloc(sizeof(struct tiny_queue_t));
queue->head = NULL;
queue->tail = NULL;
queue->length = 0;
queue->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
queue->wakeup = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
return queue;
}
void tiny_queue_free(tiny_queue_t *queue) {
struct tiny_msg_t *current_head = queue->head, *tmp = NULL;
while (current_head != NULL) {
free(current_head->data);
tmp = current_head;
current_head = current_head->next;
free(tmp);
}
free(queue);
}
int tiny_queue_push(tiny_queue_t *queue, void *data) {
int rc = pthread_mutex_lock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_lock: %d\n", rc);
return 0;
}
struct tiny_msg_t* new_node = (struct tiny_msg_t*)malloc(sizeof(struct tiny_msg_t));
new_node->data = data;
new_node->next = NULL;
queue->length++;
if (queue->head == NULL && queue->tail == NULL){
queue->head = queue->tail = new_node;
}
else {
queue->tail->next = new_node;
queue->tail = new_node;
}
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
return 0;
}
rc = pthread_cond_signal(&queue->wakeup);
if (rc != 0) {
printf("Error in pthread_cond_signal: %d\n", rc);
return 0;
}
return 1;
}
int tiny_queue_length(tiny_queue_t *queue, int timeout) {
timeout = timeout * 1000;
int rc = pthread_mutex_lock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_lock: %d\n", rc);
return 0;
}
if (timeout > 0 && queue->length == 0) {
struct timespec max_wait = {0, 0};
clock_gettime(CLOCK_REALTIME, &max_wait);
//timeout in ms
if (max_wait.tv_nsec <= (999999999 - timeout)) {
max_wait.tv_nsec += timeout;
} else {
max_wait.tv_sec += 1;
max_wait.tv_nsec = timeout - (999999999 - max_wait.tv_nsec);
}
rc = pthread_cond_timedwait(&queue->wakeup, &queue->mutex, &max_wait);
if (rc != 0 && rc != ETIMEDOUT) {
printf("Error in pthread_cond_timedwait: %d\n", rc);
}
}
unsigned len = queue->length;
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return len;
}
void *tiny_queue_shift(tiny_queue_t *queue, int timeout) {
timeout = timeout * 1000;
int rc = pthread_mutex_lock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_lock: %d\n", rc);
return 0;
}
if (queue->length == 0) {
if (timeout > 0) {
struct timespec max_wait = {0, 0};
clock_gettime(CLOCK_REALTIME, &max_wait);
//timeout in ms
if (max_wait.tv_nsec <= (999999999 - timeout)) {
max_wait.tv_nsec += timeout;
} else {
max_wait.tv_sec += 1;
max_wait.tv_nsec = timeout - (999999999 - max_wait.tv_nsec);
}
rc = pthread_cond_timedwait(&queue->wakeup, &queue->mutex, &max_wait);
if (rc != 0) {
if (rc != ETIMEDOUT) {
printf("Error in pthread_cond_timedwait: %d\n", rc);
printf("nsec: %ld\n", max_wait.tv_nsec);
}
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return NULL;
}
}
else {
rc = pthread_cond_wait(&queue->wakeup, &queue->mutex);
if (rc != 0) {
printf("Error in pthread_cond_wait: %d\n", rc);
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return NULL;
}
}
}
//queue has entry
if (queue->head != NULL) {
struct tiny_msg_t* current_head = queue->head;
void *data = current_head->data;
if (queue->head == queue->tail) {
queue->head = queue->tail = NULL;
}
else {
queue->head = queue->head->next;
}
free(current_head);
queue->length--;
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return data;
}
else {
rc = pthread_mutex_unlock(&queue->mutex);
if (rc != 0) {
printf("Error in pthread_mutex_unlock: %d\n", rc);
}
return NULL;
}
}

41
src/tiny_queue.h Normal file
View File

@ -0,0 +1,41 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __TINY_QUEUE_H__
#define __TINY_QUEUE_H__
typedef struct tiny_msg_t {
void *data;
struct tiny_msg_t *next;
} tiny_msg_t;
typedef struct tiny_queue_t {
unsigned length;
struct tiny_msg_t *head;
struct tiny_msg_t *tail;
pthread_mutex_t mutex;
pthread_cond_t wakeup;
} tiny_queue_t;
tiny_queue_t *tiny_queue_create(void);
void tiny_queue_free(tiny_queue_t *queue);
int tiny_queue_push(struct tiny_queue_t *queue, void *data);
void *tiny_queue_shift(struct tiny_queue_t *queue, int timeout);
int tiny_queue_length(struct tiny_queue_t *queue, int timeout);
#endif

View File

@ -1,55 +0,0 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include "validate.h"
void sanitize_string(const char *data) {
static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890_-. ";
char *cp = data;
const char *end = data + strlen(data);
for (cp += strspn(cp, ok_chars); cp != end; cp += strspn(cp, ok_chars))
*cp = '_';
}
int validate_path(char *path, const char *basepath) {
char *rpath = NULL;
char *ptr;
ptr = realpath(path, rpath);
if (ptr == NULL)
return 1;
if (strncmp(basepath, ptr, strlen(basepath)) == 0) {
free(rpath);
return 0;
}
else {
free(rpath);
return 1;
}
}

276
src/web_server.c Normal file
View File

@ -0,0 +1,276 @@
/* myMPD
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <limits.h>
#include <stdbool.h>
#include <pthread.h>
#include "list.h"
#include "tiny_queue.h"
#include "global.h"
#include "web_server.h"
#include "mpd_client.h"
#include "../dist/src/mongoose/mongoose.h"
#include "../dist/src/frozen/frozen.h"
//private definitions
static int is_websocket(const struct mg_connection *nc);
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data);
static void ev_handler_redirect(struct mg_connection *nc_http, int ev, void *ev_data);
static void send_ws_notify(struct mg_mgr *mgr, t_work_result *response);
static void send_api_response(struct mg_mgr *mgr, t_work_result *response);
static bool handle_api(long conn_id, const char *request, int request_len);
typedef struct t_user_data {
void *config; //pointer to mympd config
long conn_id;
} t_user_data;
//public functions
bool web_server_init(void *arg_mgr, t_config *config) {
struct mg_mgr *mgr = (struct mg_mgr *) arg_mgr;
struct mg_connection *nc_https;
struct mg_connection *nc_http;
struct mg_bind_opts bind_opts_https;
struct mg_bind_opts bind_opts_http;
const char *err_https;
const char *err_http;
t_user_data *user_data = (t_user_data*)malloc(sizeof(t_user_data));
user_data->config = config;
user_data->conn_id = 1;
mg_mgr_init(mgr, NULL);
//bind to webport
memset(&bind_opts_http, 0, sizeof(bind_opts_http));
bind_opts_http.user_data = (void *)user_data;
bind_opts_http.error_string = &err_http;
if (config->ssl == true)
nc_http = mg_bind_opt(mgr, config->webport, ev_handler_redirect, bind_opts_http);
else
nc_http = mg_bind_opt(mgr, config->webport, ev_handler, bind_opts_http);
if (nc_http == NULL) {
printf("Error listening on port %s\n", config->webport);
mg_mgr_free(mgr);
return false;
}
mg_set_protocol_http_websocket(nc_http);
LOG_INFO() printf("Listening on http port %s.\n", config->webport);
//bind to sslport
if (config->ssl == true) {
memset(&bind_opts_https, 0, sizeof(bind_opts_https));
bind_opts_https.user_data = (void *)user_data;
bind_opts_https.error_string = &err_https;
bind_opts_https.ssl_cert = config->sslcert;
bind_opts_https.ssl_key = config->sslkey;
nc_https = mg_bind_opt(mgr, config->sslport, ev_handler, bind_opts_https);
if (nc_https == NULL) {
printf("Error listening on port %s: %s\n", config->sslport, err_https);
mg_mgr_free(mgr);
return false;
}
LOG_INFO() printf("Listening on ssl port %s\n", config->sslport);
mg_set_protocol_http_websocket(nc_https);
}
return mgr;
}
void web_server_free(void *arg_mgr) {
struct mg_mgr *mgr = (struct mg_mgr *) arg_mgr;
mg_mgr_free(mgr);
}
void *web_server_loop(void *arg_mgr) {
struct mg_mgr *mgr = (struct mg_mgr *) arg_mgr;
while (s_signal_received == 0) {
mg_mgr_poll(mgr, 50);
// unsigned web_server_queue_length = tiny_queue_length(web_server_queue, 100);
// if (web_server_queue_length > 0) {
t_work_result *response = tiny_queue_shift(web_server_queue, 50);
if (response != NULL) {
if (response->conn_id == 0) {
//Websocket notify from mpd idle
send_ws_notify(mgr, response);
}
else {
//api response
send_api_response(mgr, response);
}
}
// }
}
return NULL;
}
//private functions
static int is_websocket(const struct mg_connection *nc) {
return nc->flags & MG_F_IS_WEBSOCKET;
}
static void send_ws_notify(struct mg_mgr *mgr, t_work_result *response) {
struct mg_connection *nc;
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (!is_websocket(nc))
continue;
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, response->data, response->length);
}
free(response);
}
static void send_api_response(struct mg_mgr *mgr, t_work_result *response) {
struct mg_connection *nc;
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (nc->user_data != NULL) {
t_user_data *user_data = (t_user_data *) nc->user_data;
if (user_data->conn_id == response->conn_id) {
mg_send_head(nc, 200, response->length, "Content-Type: application/json");
mg_printf(nc, "%s", response->data);
}
}
}
free(response);
}
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
t_user_data *user_data = (t_user_data *) nc->user_data;
t_config *config = (t_config *) user_data->config;
switch(ev) {
case MG_EV_ACCEPT: {
//increment conn_id
if (user_data->conn_id < LONG_MAX)
user_data->conn_id++;
else
user_data->conn_id = 1;
//replace mgr user_data with connection specific user_data
t_user_data *nc_user_data = (t_user_data*)malloc(sizeof(t_user_data));
nc_user_data->config = config;
nc_user_data->conn_id = user_data->conn_id;
nc->user_data = nc_user_data;
LOG_DEBUG() fprintf(stderr, "DEBUG: New connection id %ld.\n", user_data->conn_id);
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("New websocket request (%ld): %.*s\n", user_data->conn_id, (int)hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
LOG_VERBOSE() printf("New Websocket connection established (%ld).\n", user_data->conn_id);
char response[] = "{\"type\": \"welcome\", \"data\": {\"mympdVersion\": \"" MYMPD_VERSION "\"}}";
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, response, strlen(response));
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
LOG_VERBOSE() printf("HTTP request (%ld): %.*s\n", user_data->conn_id, (int)hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0) {
bool rc = handle_api(user_data->conn_id, hm->body.p, hm->body.len);
if (rc == false) {
printf("ERROR: Invalid API request.\n");
const char *response = "{\"type\": \"error\", \"data\": \"Invalid API request\"}";
mg_send_head(nc, 200, strlen(response), "Content-Type: application/json");
mg_printf(nc, "%s", response);
}
}
else {
static struct mg_serve_http_opts s_http_server_opts;
s_http_server_opts.document_root = DOC_ROOT;
s_http_server_opts.enable_directory_listing = "no";
mg_serve_http(nc, hm, s_http_server_opts);
}
break;
}
case MG_EV_CLOSE: {
LOG_VERBOSE() fprintf(stderr, "HTTP connection %ld closed.\n", user_data->conn_id);
free(nc->user_data);
break;
}
default: {
break;
}
}
}
static void ev_handler_redirect(struct mg_connection *nc, int ev, void *ev_data) {
char *host;
char *crap;
char host_header[1024];
switch(ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
t_user_data *user_data = (t_user_data *) nc->user_data;
t_config *config = (t_config *) user_data->config;
snprintf(host_header, 1024, "%.*s", (int)host_hdr->len, host_hdr->p);
host = strtok_r(host_header, ":", &crap);
char s_redirect[250];
if (strcmp(config->sslport, "443") == 0)
snprintf(s_redirect, 250, "https://%s/", host);
else
snprintf(s_redirect, 250, "https://%s:%s/", host, config->sslport);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
default: {
break;
}
}
}
static bool handle_api(long conn_id, const char *request_body, int request_len) {
char *cmd;
LOG_VERBOSE() printf("API request (%ld): %.*s\n", conn_id, request_len, request_body);
const int je = json_scanf(request_body, request_len, "{cmd: %Q}", &cmd);
if (je < 1)
return false;
enum mympd_cmd_ids cmd_id = get_cmd_id(cmd);
if (cmd_id == 0)
return false;
t_work_request *request = (t_work_request*)malloc(sizeof(t_work_request));
request->conn_id = conn_id;
request->cmd_id = cmd_id;
request->length = copy_string(request->data, request_body, 1000, request_len);
if (strncmp(cmd, "MYMPD_API_", 10) == 0)
tiny_queue_push(mympd_api_queue, request);
else
tiny_queue_push(mpd_client_queue, request);
free(cmd);
return true;
}

View File

@ -1,9 +1,9 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/ympd
(c) 2018-2019 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
@ -22,13 +22,11 @@
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __CONFIG_H__
#define __CONFIG_H__
#ifndef __WEB_SERVER_H__
#define __WEB_SERVER_H__
void *web_server_loop(void *arg_mgr);
bool web_server_init(void *arg_mgr, t_config *config);
void web_server_free(void *arg_mgr);
#define MYMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}
#define MYMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}
#define MYMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
#define MYMPD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}"
#define SRC_PATH "${ASSETS_PATH}"
#cmakedefine DEBUG
#endif