mirror of
https://github.com/SuperBFG7/ympd
synced 2024-11-14 10:04:48 +00:00
Merge pull request #90 from jcorporation/devel
Merge devel into master for 5.0.0 release
This commit is contained in:
commit
a5a3d16b8a
@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
project (mympd C)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "4")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "7")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "2")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "5")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "0")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "0")
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES RELEASE)
|
||||
set(ASSETS_PATH "/usr/share/${PROJECT_NAME}/htdocs")
|
||||
@ -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)
|
||||
|
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@ -0,0 +1,42 @@
|
||||
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_LOGLEVEL=1
|
||||
ENV MPD_MPDHOST=127.0.0.1
|
||||
ENV MPD_MPDPORT=6600
|
||||
ENV WEBSERVER_SSL=true
|
||||
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"]
|
2
PKGBUILD
2
PKGBUILD
@ -4,7 +4,7 @@
|
||||
|
||||
pkgname=mympd
|
||||
_pkgname=myMPD
|
||||
pkgver=4.7.2
|
||||
pkgver=5.0.0
|
||||
pkgrel=1
|
||||
pkgdesc="myMPD is a standalone and mobile friendly web mpdclient."
|
||||
arch=('x86_64' 'armv7h' 'aarch64')
|
||||
|
@ -3,8 +3,8 @@ myMPD
|
||||
|
||||
myMPD is a lightweight MPD web client that runs without a dedicated webserver or interpreter.
|
||||
It's tuned for minimal resource usage and requires only very litte dependencies.
|
||||
myMPD is a fork of ympd (https://github.com/notandy/ympd).
|
||||
|
||||
myMPD is a fork of ympd (https://github.com/notandy/ympd).
|
||||
This fork provides a reworked ui based on Bootstrap 4, a modernized backend and many new features while having the same small footprint as ympd.
|
||||
|
||||
**Design principles:**
|
||||
@ -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)
|
||||
@ -29,6 +30,7 @@ This fork provides a reworked ui based on Bootstrap 4, a modernized backend and
|
||||
- Local playback of mpd http stream (html5 audio api)
|
||||
- Progressiv Web App enabled
|
||||
- Embedded Webserver (mongoose)
|
||||
- Docker support
|
||||
|
||||
myMPD is work in progress. Bugreportes and feature requests are very welcome.
|
||||
- https://github.com/jcorporation/myMPD/issues
|
||||
@ -72,7 +74,7 @@ Unix Build Instructions
|
||||
Run
|
||||
---------
|
||||
```
|
||||
Usage: ./mympd /etc/mypd/mympd.conf
|
||||
Usage: ./mympd [/etc/mympd/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.
|
||||
|
||||
@ -86,6 +88,6 @@ SSL
|
||||
|
||||
Copyright
|
||||
---------
|
||||
myMPD: 2018 - 2019 <mail@jcgames.de>
|
||||
|
||||
myMPD: 2018-2019 <mail@jcgames.de>
|
||||
ympd: 2013-2014 <andy@ndyk.de>
|
||||
|
@ -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
|
||||
|
29
contrib/docker/compose
Normal file
29
contrib/docker/compose
Normal file
@ -0,0 +1,29 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
mympd:
|
||||
container_name: mympd
|
||||
# environment:
|
||||
# MPD_MPDHOST: 192.168.1.1
|
||||
# MPD_MPDPORT: 6600
|
||||
# WEBSERVER_SSL: true
|
||||
# MYMPD_LOGLEVEL: 2
|
||||
# image: mympd:latest
|
||||
build: $MYMPD_BUILD_PATH
|
||||
networks:
|
||||
- mympd
|
||||
ports:
|
||||
- "40000:80"
|
||||
- "40001:443"
|
||||
restart: always
|
||||
volumes:
|
||||
- /path/to/music/:/usr/share/mympd/htdocs/library/:ro
|
||||
- mympd:/etc/mympd/
|
||||
|
||||
networks:
|
||||
|
||||
mympd:
|
||||
|
||||
volumes:
|
||||
|
||||
mympd:
|
4
contrib/docker/init.sh
Normal file
4
contrib/docker/init.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
/postinst
|
||||
mympd
|
@ -4,7 +4,7 @@
|
||||
# (c) 2018 Juergen Mang <mail@jcgames.de>
|
||||
|
||||
Name: myMPD
|
||||
Version: 4.7.2
|
||||
Version: 5.0.0
|
||||
Release: 0
|
||||
License: GPL-2.0
|
||||
Group: Productivity/Multimedia/Sound/Players
|
||||
@ -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
|
||||
|
@ -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
5
debian/changelog
vendored
@ -1,5 +1,4 @@
|
||||
mympd (4.7.2-1) stable; urgency=medium
|
||||
|
||||
mympd (5.0.0-1) stable; urgency=medium
|
||||
* Release from master
|
||||
|
||||
-- Juergen Mang <mail@jcgames.de> Fri, 07 Dec 2018 17:12:12 +0000
|
||||
-- Juergen Mang <mail@jcgames.de> Fri, 04 Jan 2019 09:01:07 +0000
|
||||
|
3
debian/postinst
vendored
3
debian/postinst
vendored
@ -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
|
||||
|
2
dist/htdocs/css/mympd.min.css
vendored
2
dist/htdocs/css/mympd.min.css
vendored
@ -1 +1 @@
|
||||
: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-backgroundcolor:#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: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:2rem}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,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;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: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:0}.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 .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;height:20px;text-align:center}ol#searchCrumb{padding:.5rem}.nodropdown::after{content:none}
|
2
dist/htdocs/index.html
vendored
2
dist/htdocs/index.html
vendored
File diff suppressed because one or more lines are too long
160
dist/htdocs/js/mympd.min.js
vendored
160
dist/htdocs/js/mympd.min.js
vendored
@ -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">×</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">×</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">×</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">×</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">×</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">×</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]+'"> '+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]+'"> '+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();
|
||||
|
2
dist/htdocs/sw.min.js
vendored
2
dist/htdocs/sw.min.js
vendored
@ -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)}))}))});
|
||||
|
2
dist/src/mongoose/mongoose.c
vendored
2
dist/src/mongoose/mongoose.c
vendored
@ -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();
|
||||
}
|
||||
|
||||
|
4
dist/src/mongoose/mongoose.h
vendored
4
dist/src/mongoose/mongoose.h
vendored
@ -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,
|
||||
|
@ -1,5 +1,6 @@
|
||||
:root {
|
||||
--mympd-coverimagesize: 240px;
|
||||
--mympd-backgroundcolor: #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;
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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"}}'
|
||||
|
@ -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) {
|
||||
@ -1363,14 +1430,23 @@ function setCols(table, className) {
|
||||
}
|
||||
document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks;
|
||||
|
||||
var sort = document.getElementById('SearchList').getAttribute('data-sort');
|
||||
if (sort == '') {
|
||||
if (settings.featTags)
|
||||
sort = 'Title';
|
||||
else
|
||||
sort = 'Filename';
|
||||
var sort = app.current.sort;
|
||||
|
||||
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 = '';
|
||||
for (var i = 0; i < settings['cols' + table].length; i++) {
|
||||
@ -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();
|
||||
|
@ -1,4 +1,4 @@
|
||||
var CACHE = 'myMPD-cache-v4.7.2';
|
||||
var CACHE = 'myMPD-cache-v5.0.0';
|
||||
var urlsToCache = [
|
||||
'/',
|
||||
'/player.html',
|
||||
|
@ -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
|
||||
|
115
mkrelease.sh
115
mkrelease.sh
@ -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
93
src/global.c
Normal 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
197
src/global.h.in
Normal 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
|
@ -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) {
|
||||
|
@ -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
|
||||
|
431
src/main.c
Normal file
431
src/main.c
Normal file
@ -0,0 +1,431 @@
|
||||
/* 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 mympd_signal_handler(int sig_num) {
|
||||
signal(sig_num, mympd_signal_handler); // Reinstantiate signal handler
|
||||
s_signal_received = sig_num;
|
||||
//Wakeup mympd_api_loop
|
||||
pthread_cond_signal(&mympd_api_queue->wakeup);
|
||||
}
|
||||
|
||||
static int mympd_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) strcasecmp(section, s) == 0 && strcasecmp(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 mympd_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);
|
||||
}
|
||||
|
||||
static void mympd_parse_env(struct t_config *config, const char *envvar) {
|
||||
char *name, *section;
|
||||
const char *value = getenv(envvar);
|
||||
if (value != NULL) {
|
||||
char *var = strdup(envvar);
|
||||
section = strtok_r(var, "_", &name);
|
||||
if (section != NULL && name != NULL) {
|
||||
LOG_DEBUG() printf("DEBUG: Using environment variable %s_%s: %s\n", section, name, value);
|
||||
mympd_inihandler(config, section, name, value);
|
||||
}
|
||||
value = NULL;
|
||||
free(var);
|
||||
}
|
||||
}
|
||||
|
||||
static void mympd_get_env(struct t_config *config) {
|
||||
const char* env_vars[]={"MPD_MPDHOST", "MPD_MPDPORT", "MPD_STREAMPORT",
|
||||
"WEBSERVER_WEBPORT", "WEBSERVER_SSL", "WEBSERVER_SSLPORT", "WEBSERVER_SSLCERT", "WEBSERVER_SSLKEY",
|
||||
"MYMPD_LOGLEVEL", "MYMPD_USER", "MYMPD_LOCALPLAYER", "MYMPD_COVERIMAGE", "MYMPD_COVERIMAGENAME",
|
||||
"MYMPD_COVERIMAGESIZE", "MYMPD_VARLIBDIR", "MYMPD_MIXRAMP", "MYMPD_STICKERS", "MYMPD_TAGLIST",
|
||||
"MYMPD_SEARCHTAGLIST", "MYMPD_BROWSETAGLIST", "MYMPD_SMARTPLS", "MYMPD_SYSCMDS",
|
||||
"MYMPD_MAX_ELEMENTS_PER_PAGE", "MYMPD_LAST_PLAYED_COUNT",
|
||||
"THEME_BACKGROUNDCOLOR", 0};
|
||||
const char** ptr = env_vars;
|
||||
while (*ptr != 0) {
|
||||
mympd_parse_env(config, *ptr);
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
config->etcdir = strdup("/etc/mympd");
|
||||
config->syscmds = false;
|
||||
config->localplayer = true;
|
||||
config->loglevel = 1;
|
||||
config->backgroundcolor = strdup("#888");
|
||||
|
||||
char *configfile = strdup("/etc/mympd/mympd.conf");
|
||||
if (argc == 2) {
|
||||
if (strncmp(argv[1], "/", 1) == 0) {
|
||||
printf("Starting myMPD %s\n", MYMPD_VERSION);
|
||||
printf("Libmpdclient %i.%i.%i\n", LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION);
|
||||
free(configfile);
|
||||
configfile = argv[1];
|
||||
char *etcdir = strdup(configfile);
|
||||
free(config->etcdir);
|
||||
config->etcdir = strdup(dirname(etcdir));
|
||||
free(etcdir);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (access(configfile, F_OK ) != -1) {
|
||||
printf("Parsing config file: %s\n", configfile);
|
||||
if (ini_parse(configfile, mympd_inihandler, config) < 0) {
|
||||
printf("Can't load config file \"%s\"\n", configfile);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("No config file found, using defaults\n");
|
||||
}
|
||||
|
||||
//read environment - overwrites config file definitions
|
||||
mympd_get_env(config);
|
||||
|
||||
#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;
|
||||
|
||||
//set signal handler
|
||||
signal(SIGTERM, mympd_signal_handler);
|
||||
signal(SIGINT, mympd_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 (getuid() == 0) {
|
||||
if (config->user != NULL || strlen(config->user) != 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (config->syscmds == true) {
|
||||
snprintf(testdirname, 400, "%s/syscmds", config->etcdir);
|
||||
if (!testdir("Syscmds directory", testdirname)) {
|
||||
printf("Disabling syscmd support\n");
|
||||
config->syscmds = false;
|
||||
}
|
||||
}
|
||||
|
||||
//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);
|
||||
mympd_free_config(config);
|
||||
free(configfile);
|
||||
return rc;
|
||||
}
|
2234
src/mpd_client.c
2234
src/mpd_client.c
File diff suppressed because it is too large
Load Diff
263
src/mpd_client.h
263
src/mpd_client.h
@ -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
|
||||
|
564
src/mympd.c
564
src/mympd.c
@ -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
520
src/mympd_api.c
Normal 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 = -1;
|
||||
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) {
|
||||
// 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);
|
||||
|
||||
//create response struct
|
||||
t_work_result *response = (t_work_result *)malloc(sizeof(t_work_result));
|
||||
response->conn_id = request->conn_id;
|
||||
response->length = 0;
|
||||
|
||||
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) {
|
||||
response->length = mympd_api_syscmd(config, mympd_state, response->data, p_charbuf1);
|
||||
free(p_charbuf1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
response->length = snprintf(response->data, 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 {
|
||||
response->length = snprintf(response->data, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown table %s\"}", p_charbuf1);
|
||||
printf("MYMPD_API_COLS_SAVE: Unknown table %s\n", p_charbuf1);
|
||||
}
|
||||
if (response->length == 0) {
|
||||
if (state_file_write(config, p_charbuf1, cols))
|
||||
response->length = snprintf(response->data, 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")))
|
||||
response->length = snprintf(response->data, 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")))
|
||||
response->length = snprintf(response->data, 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")))
|
||||
response->length = snprintf(response->data, 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))
|
||||
response->length = snprintf(response->data, 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))
|
||||
response->length = snprintf(response->data, 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))
|
||||
response->length = snprintf(response->data, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Can't set state jukeboxQueueLength.\"}");
|
||||
}
|
||||
if (response->length == 0) {
|
||||
response->length = snprintf(response->data, 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 = -1;
|
||||
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) {
|
||||
response->length = mympd_api_put_settings(config, mympd_state, response->data);
|
||||
}
|
||||
else {
|
||||
response->length = snprintf(response->data, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown cmd_id %u.\"}", request->cmd_id);
|
||||
printf("ERROR: Unknown cmd_id %u\n", request->cmd_id);
|
||||
}
|
||||
|
||||
if (response->length == 0) {
|
||||
response->length = snprintf(response->data, 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, response->data);
|
||||
|
||||
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();
|
||||
}
|
@ -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
179
src/tiny_queue.c
Normal 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
41
src/tiny_queue.h
Normal 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
|
@ -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
276
src/web_server.c
Normal 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;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/* 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:
|
||||
|
||||
@ -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
|
Loading…
Reference in New Issue
Block a user