mirror of
				https://github.com/SuperBFG7/ympd
				synced 2025-10-31 13:53:00 +00:00 
			
		
		
		
	Merge pull request #90 from jcorporation/devel
Merge devel into master for 5.0.0 release
This commit is contained in:
		| @@ -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) { | ||||
| @@ -1362,14 +1429,23 @@ function setCols(table, className) { | ||||
|             '</div>'; | ||||
|     } | ||||
|     document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks; | ||||
|  | ||||
|     var sort = app.current.sort; | ||||
|      | ||||
|     var sort = document.getElementById('SearchList').getAttribute('data-sort'); | ||||
|     if (sort == '') { | ||||
|         if (settings.featTags) | ||||
|             sort = 'Title'; | ||||
|         else | ||||
|             sort = 'Filename'; | ||||
|     if (table == 'Search') { | ||||
|         if (app.apps.Search.state == '0/any/Title/') { | ||||
|             if (settings.tags.includes('Title')) { | ||||
|                 sort = 'Title'; | ||||
|             } | ||||
|             else if (settings.featTags == false) { | ||||
|                 sort = 'Filename'; | ||||
|             } | ||||
|             else { | ||||
|                 sort = '-'; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|          | ||||
|      | ||||
|     if (table != 'Playback') { | ||||
|         var heading = ''; | ||||
| @@ -1380,11 +1456,10 @@ function setCols(table, className) { | ||||
|                 h = '#'; | ||||
|             heading += h; | ||||
|  | ||||
|             if (table == 'Search' && h == sort ) { | ||||
|             if (table == 'Search' && (h == sort || '-' + h == sort) ) { | ||||
|                 var sortdesc = false; | ||||
|                 if (sort.indexOf('-') == 0) { | ||||
|                 if (app.current.sort.indexOf('-') == 0) { | ||||
|                     sortdesc = true; | ||||
|                     sort = sort.substring(1); | ||||
|                 } | ||||
|                 heading += '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>'; | ||||
|             } | ||||
| @@ -1404,7 +1479,24 @@ function setCols(table, className) { | ||||
| } | ||||
|  | ||||
| function getSettings() { | ||||
|     sendAPI({"cmd": "MPD_API_SETTINGS_GET"}, parseSettings); | ||||
|     if (settingsLock == false) { | ||||
|         settingsLock = true; | ||||
|         sendAPI({"cmd": "MYMPD_API_SETTINGS_GET"}, getMpdSettings); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function getMpdSettings(obj) { | ||||
|     settingsNew = obj.data; | ||||
|     sendAPI({"cmd": "MPD_API_SETTINGS_GET"}, joinSettings); | ||||
| } | ||||
|  | ||||
| function joinSettings(obj) { | ||||
|     for (var key in obj.data) { | ||||
|         settingsNew[key] = obj.data[key]; | ||||
|     } | ||||
|     settings = Object.assign({}, settingsNew); | ||||
|     settingsLock = false; | ||||
|     parseSettings(); | ||||
| } | ||||
|  | ||||
| function saveCols(table, tableEl) { | ||||
| @@ -1431,7 +1523,7 @@ function saveCols(table, tableEl) { | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var cols = {"cmd": "MPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}}; | ||||
|     var cols = {"cmd": "MYMPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}}; | ||||
|     var ths = header.getElementsByTagName('th'); | ||||
|     for (var i = 0; i < ths.length; i++) { | ||||
|         var name = ths[i].getAttribute('data-col'); | ||||
| @@ -1460,7 +1552,7 @@ function saveColsPlayback(table) { | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var cols = {"cmd": "MPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}}; | ||||
|     var cols = {"cmd": "MYMPD_API_COLS_SAVE", "data": {"table": "cols" + table, "cols": []}}; | ||||
|     var ths = header.getElementsByTagName('div'); | ||||
|     for (var i = 0; i < ths.length; i++) { | ||||
|         var name = ths[i].getAttribute('data-tag'); | ||||
| @@ -1585,7 +1677,7 @@ function parseState(obj) { | ||||
|     setCounter(obj.data.currentSongId, obj.data.totalTime, obj.data.elapsedTime); | ||||
|      | ||||
|     //Get current song | ||||
|     if (lastState && lastState.data.currentSongId != obj.data.currentSongId) | ||||
|     if (!lastState || lastState.data.currentSongId != obj.data.currentSongId) | ||||
|         sendAPI({"cmd": "MPD_API_PLAYER_CURRENT_SONG"}, songChange); | ||||
|     //clear playback card if not playing | ||||
|     if (obj.data.songPos == '-1') { | ||||
| @@ -2182,7 +2274,7 @@ function gotoBrowse(x) { | ||||
|     var tag = x.parentNode.getAttribute('data-tag'); | ||||
|     var name = decodeURI(x.parentNode.getAttribute('data-name')); | ||||
|     if (tag != '' && name != '' && name != '-' && settings.browsetags.includes(tag))  | ||||
|         appGoto('Browse', 'Database', tag, '0/-/' + name); | ||||
|         appGoto('Browse', 'Database', tag, '0/-/-/' + name); | ||||
| } | ||||
|  | ||||
| function songDetails(uri) { | ||||
| @@ -2232,12 +2324,12 @@ function parseSongDetails(obj) { | ||||
| } | ||||
|  | ||||
| function execSyscmd(cmd) { | ||||
|     sendAPI({"cmd": "MPD_API_SYSCMD", "data": {"cmd": cmd}}); | ||||
|     sendAPI({"cmd": "MYMPD_API_SYSCMD", "data": {"cmd": cmd}}); | ||||
| } | ||||
|  | ||||
| function playlistDetails(uri) { | ||||
|     document.getElementById('BrowsePlaylistsAllList').classList.add('opacity05'); | ||||
|     appGoto('Browse', 'Playlists', 'Detail', '0/-/' + uri); | ||||
|     appGoto('Browse', 'Playlists', 'Detail', '0/-/-/' + uri); | ||||
| } | ||||
|  | ||||
| function removeFromPlaylist(uri, pos) { | ||||
| @@ -2865,7 +2957,7 @@ function confirmSettings() { | ||||
|         var selectReplaygain = document.getElementById('selectReplaygain'); | ||||
|         var selectJukeboxPlaylist = document.getElementById('selectJukeboxPlaylist'); | ||||
|         var selectJukeboxMode = document.getElementById('selectJukeboxMode'); | ||||
|         sendAPI({"cmd": "MPD_API_SETTINGS_SET", "data": { | ||||
|         sendAPI({"cmd": "MYMPD_API_SETTINGS_SET", "data": { | ||||
|             "consume": (document.getElementById('btnConsume').classList.contains('active') ? 1 : 0), | ||||
|             "random":  (document.getElementById('btnRandom').classList.contains('active') ? 1 : 0), | ||||
|             "single":  (document.getElementById('btnSingle').classList.contains('active') ? 1 : 0), | ||||
| @@ -2878,7 +2970,8 @@ function confirmSettings() { | ||||
|             "notificationPage": (document.getElementById('btnnotifyPage').classList.contains('active') ? true : false), | ||||
|             "jukeboxMode": selectJukeboxMode.options[selectJukeboxMode.selectedIndex].value, | ||||
|             "jukeboxPlaylist": selectJukeboxPlaylist.options[selectJukeboxPlaylist.selectedIndex].value, | ||||
|             "jukeboxQueueLength": document.getElementById('inputJukeboxQueueLength').value | ||||
|             "jukeboxQueueLength": document.getElementById('inputJukeboxQueueLength').value, | ||||
|             "autoPlay": (document.getElementById('btnAutoPlay').classList.contains('active') ? true : false) | ||||
|         }}, getSettings); | ||||
|         modalSettings.hide(); | ||||
|     } else | ||||
| @@ -2926,7 +3019,7 @@ function gotoPage(x) { | ||||
|         default: | ||||
|             app.current.page = x; | ||||
|     } | ||||
|     appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.search); | ||||
|     appGoto(app.current.app, app.current.tab, app.current.view, app.current.page + '/' + app.current.filter + '/' + app.current.sort + '/' + app.current.search); | ||||
| } | ||||
|  | ||||
| function saveQueue() { | ||||
| @@ -3087,7 +3180,7 @@ function addFilterLetter(x) { | ||||
|             default: | ||||
|                 filter = event.target.innerText; | ||||
|         } | ||||
|         appGoto(app.current.app, app.current.tab, app.current.view, '0/' + filter + '/' + app.current.search); | ||||
|         appGoto(app.current.app, app.current.tab, app.current.view, '0/' + filter + '/' + app.current.sort + '/' + app.current.search); | ||||
|     }, false); | ||||
| } | ||||
|  | ||||
| @@ -3116,7 +3209,7 @@ function addTagList(el, list) { | ||||
| } | ||||
|  | ||||
| function gotoTagList() { | ||||
|     appGoto(app.current.app, app.current.tab, app.current.view, '0/-/'); | ||||
|     appGoto(app.current.app, app.current.tab, app.current.view, '0/-/-/'); | ||||
| } | ||||
|  | ||||
| function openModal(modal) { | ||||
| @@ -3162,4 +3255,4 @@ function genId(x) { | ||||
| } | ||||
|  | ||||
| //Init app | ||||
| appInit(); | ||||
| appInitStart(); | ||||
|   | ||||
| @@ -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,9 +1,9 @@ | ||||
| /* myMPD
 | ||||
|    (c) 2018 Juergen Mang <mail@jcgames.de> | ||||
|    This project's homepage is: https://github.com/jcorporation/ympd
 | ||||
|    (c) 2018-2019 Juergen Mang <mail@jcgames.de> | ||||
|    This project's homepage is: https://github.com/jcorporation/mympd
 | ||||
|     | ||||
|    myMPD ist fork of: | ||||
| 
 | ||||
|     | ||||
|    ympd | ||||
|    (c) 2013-2014 Andrew Karpow <andy@ndyk.de> | ||||
|    This project's homepage is: http://www.ympd.org
 | ||||
| @@ -22,13 +22,11 @@ | ||||
|    Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
| */ | ||||
| 
 | ||||
| #ifndef __CONFIG_H__ | ||||
| #define __CONFIG_H__ | ||||
| #ifndef __WEB_SERVER_H__ | ||||
| #define __WEB_SERVER_H__ | ||||
| 
 | ||||
| void *web_server_loop(void *arg_mgr); | ||||
| bool web_server_init(void *arg_mgr, t_config *config); | ||||
| void web_server_free(void *arg_mgr); | ||||
| 
 | ||||
| #define MYMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR} | ||||
| #define MYMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR} | ||||
| #define MYMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH} | ||||
| #define MYMPD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}" | ||||
| #define SRC_PATH "${ASSETS_PATH}" | ||||
| #cmakedefine DEBUG | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user
	 Jürgen Mang
					Jürgen Mang