From 8d94cd9cabf35514d6928544fd2274ad26149d5b Mon Sep 17 00:00:00 2001 From: jcorporation Date: Mon, 9 Jul 2018 22:58:25 +0100 Subject: [PATCH 01/22] Enable ssl options --- CMakeLists.txt | 14 ++++--- README.md | 4 ++ mympd.1 | 14 ++++++- src/mongoose/mongoose.c | 1 - src/mongoose/mongoose.h | 2 +- src/mympd.c | 90 ++++++++++++++++++++++++++++++++++++----- 6 files changed, 106 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e5bf6e..fabe703 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 2.6) project (mympd C) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/") set(CPACK_PACKAGE_VERSION_MAJOR "3") -set(CPACK_PACKAGE_VERSION_MINOR "1") -set(CPACK_PACKAGE_VERSION_PATCH "1") +set(CPACK_PACKAGE_VERSION_MINOR "2") +set(CPACK_PACKAGE_VERSION_PATCH "0") if(CMAKE_BUILD_TYPE MATCHES RELEASE) set(ASSETS_PATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/htdocs") @@ -12,7 +12,6 @@ if(CMAKE_BUILD_TYPE MATCHES RELEASE) else() set(ASSETS_PATH "${PROJECT_SOURCE_DIR}/htdocs") set(DEBUG "ON") - set(CS_NDEBUG "ON") endif() find_package(LibMPDClient REQUIRED) @@ -23,9 +22,13 @@ include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_I include(CheckCSourceCompiles) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -pedantic -D MG_DISABLE_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 "${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") +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/mpd_client.c @@ -34,7 +37,7 @@ set(SOURCES ) add_executable(mympd ${SOURCES}) -target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES}) install(TARGETS mympd DESTINATION bin) install(FILES mympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1) @@ -47,3 +50,4 @@ install(FILES htdocs/css/bootstrap.min.css DESTINATION share/${PROJECT_NAME}/htd install(FILES htdocs/css/mpd.min.css DESTINATION share/${PROJECT_NAME}/htdocs/css/) install(DIRECTORY htdocs/assets DESTINATION share/${PROJECT_NAME}/htdocs) install(DIRECTORY DESTINATION /var/lib/${PROJECT_NAME}/) +install(DIRECTORY DESTINATION /etc/${PROJECT_NAME}/) diff --git a/README.md b/README.md index 6733e5c..e6fcd84 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Usage: ./mympd [OPTION]... -h, --host connect to mpd at host [localhost] -p, --port connect to mpd at port [6600] -w, --webport listen port for webserver [80] + -S, --ssl enable ssl + -W, --sslport listen port for ssl webserver [443] + -C, --sslcert filename for ssl certificate [/etc/mympd/server.pem] + -K, --sslkey filename for ssl key [/etc/mympd/server.key] -s, --streamport connect to mpd http stream at port [8000] -u, --user drop priviliges to user after socket bind -m, --mpdpass specifies the password to use when connecting to mpd diff --git a/mympd.1 b/mympd.1 index 92bf375..aa06809 100644 --- a/mympd.1 +++ b/mympd.1 @@ -19,7 +19,19 @@ connect to mpd at host, defaults to localhost connect to mpd at port, defaults to 6600 .TP \fB\-w\fR, \fB\-\-webport PORT\fR -specifies the port for the webserver to listen to, defaults to 8080 +listen interface/port for webserver [80] +.TP +\fB\-S\fR, \fB\-\-ssl\fR +enable ssl +.TP +\fB\-W\fR, \fB\-\-sslport PORT\fR +listen interface/port for ssl webserver [443] +.TP +\fB\-C\fR, \fB\-\-sslcert FILENAME\fR +filename for ssl certificate [/etc/mympd/server.pem] +.TP +\fB\-K\fR, \fB\-\-sslkey FILENAME\fR +filename for ssl key [/etc/mympd/server.key] .TP \fB-s\fR, \fB\-\-streamport PORT connect to mpd http stream at port [8000] diff --git a/src/mongoose/mongoose.c b/src/mongoose/mongoose.c index 0bb94ae..141e501 100644 --- a/src/mongoose/mongoose.c +++ b/src/mongoose/mongoose.c @@ -9479,7 +9479,6 @@ static struct mg_ws_proto_data *mg_ws_get_proto_data(struct mg_connection *nc) { */ static void mg_ws_close(struct mg_connection *nc, const void *data, size_t len) { - printf("SEND: %.*s\n",len,data); if ((int) len == ~0) { len = strlen((const char *) data); } diff --git a/src/mongoose/mongoose.h b/src/mongoose/mongoose.h index 2529bdf..eecbbef 100644 --- a/src/mongoose/mongoose.h +++ b/src/mongoose/mongoose.h @@ -4682,7 +4682,7 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, #ifdef __GNUC__ __attribute__((deprecated)); #endif - +; /* * Gets and parses the Authorization: Basic header diff --git a/src/mympd.c b/src/mympd.c index dbf47c0..df197dd 100644 --- a/src/mympd.c +++ b/src/mympd.c @@ -37,6 +37,7 @@ extern char *optarg; static sig_atomic_t s_signal_received = 0; static struct mg_serve_http_opts s_http_server_opts; +char s_redirect[250]; static void signal_handler(int sig_num) { signal(sig_num, signal_handler); // Reinstantiate signal handler @@ -44,14 +45,14 @@ static void signal_handler(int sig_num) { } static void handle_api(struct mg_connection *nc, struct http_message *hm) { - if(!is_websocket(nc)) { + 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}; memcpy(buf, hm->body.p,sizeof(buf) - 1 < hm->body.len ? sizeof(buf) - 1 : hm->body.len); struct mg_str d = {buf, strlen(buf)}; callback_mympd(nc, d); - if(!is_websocket(nc)) { + if (!is_websocket(nc)) { mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */ } } @@ -82,7 +83,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { case MG_EV_CLOSE: { if (is_websocket(nc)) { #ifdef DEBUG - fprintf(stdout,"Websocket connection closed\n"); + printf("Websocket connection closed\n"); #endif mympd_close_handler(nc); } @@ -96,24 +97,49 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { } } +static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data) { + switch(ev) { + case MG_EV_HTTP_REQUEST: { + + printf("Redirecting to %s\n", s_redirect); + mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL)); + break; + } + } +} + int main(int argc, char **argv) { int n, option_index = 0; struct mg_mgr mgr; struct mg_connection *nc; + struct mg_connection *nc_http; unsigned int current_timer = 0, last_timer = 0; char *run_as_user = NULL; char *webport = "80"; + char *sslport = "443"; mpd.port = 6600; strcpy(mpd.host, "127.0.0.1"); streamport = 8000; strcpy(coverimage, "folder.jpg"); mpd.statefile="/var/lib/mympd/mympd.state"; + struct mg_bind_opts bind_opts; + const char *err; + bool ssl = false; + char *s_ssl_cert = "/etc/mympd/server.pem"; + char *s_ssl_key = "/etc/mympd/server.key"; + char hostname[1024]; + hostname[1023] = '\0'; + gethostname(hostname, 1023); static struct option long_options[] = { {"host", required_argument, 0, 'h'}, {"port", required_argument, 0, 'p'}, {"webport", required_argument, 0, 'w'}, + {"ssl", no_argument, 0, 'S'}, + {"sslport", required_argument, 0, 'W'}, + {"sslcert", required_argument, 0, 'C'}, + {"sslkey", required_argument, 0, 'K'}, {"user", required_argument, 0, 'u'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 0 }, @@ -124,7 +150,7 @@ int main(int argc, char **argv) {0, 0, 0, 0 } }; - while((n = getopt_long(argc, argv, "D:h:p:w:u:vm:s:i:c:t:", + while((n = getopt_long(argc, argv, "h:p:w:SW:C:K:u:vm:s:i:t:", long_options, &option_index)) != -1) { switch (n) { case 't': @@ -139,6 +165,18 @@ int main(int argc, char **argv) case 'w': webport = strdup(optarg); break; + case 'S': + ssl = true; + break; + case 'W': + sslport = strdup(optarg); + break; + case 'C': + s_ssl_cert = strdup(optarg); + break; + case 'K': + s_ssl_key = strdup(optarg); + break; case 'u': run_as_user = strdup(optarg); break; @@ -163,7 +201,11 @@ int main(int argc, char **argv) fprintf(stderr, "Usage: %s [OPTION]...\n\n" " -h, --host \t\tconnect to mpd at host [localhost]\n" " -p, --port \t\tconnect to mpd at port [6600]\n" - " -w, --webport [ip:]\tlisten interface/port for webserver [8080]\n" + " -w, --webport [ip:]\tlisten interface/port for webserver [80]\n" + " -S, --ssl\tenable ssl\n" + " -W, --sslport [ip:]\tlisten interface/port for ssl webserver [443]\n" + " -C, --sslcert \tfilename for ssl certificate [/etc/mympd/server.pem]\n" + " -K, --sslkey \tfilename for ssl key [/etc/mympd/server.key]\n" " -u, --user \t\tdrop priviliges to user after socket bind\n" " -v, --version\t\t\tget version\n" " -m, --mpdpass \tspecifies the password to use when connecting to mpd\n" @@ -184,10 +226,29 @@ int main(int argc, char **argv) mg_mgr_init(&mgr, NULL); - nc = mg_bind(&mgr, webport, ev_handler); - if (nc == NULL) { - fprintf(stderr, "Error starting server on port %s\n", webport); - return EXIT_FAILURE; + if (ssl == true) { + snprintf(s_redirect, 200, "https://%s:%s/", hostname, sslport); + nc_http = mg_bind(&mgr, webport, ev_handler_http); + if (nc_http == NULL) { + fprintf(stderr, "Error starting server on port %s", webport ); + return EXIT_FAILURE; + } + memset(&bind_opts, 0, sizeof(bind_opts)); + bind_opts.ssl_cert = s_ssl_cert; + bind_opts.ssl_key = s_ssl_key; + bind_opts.error_string = &err; + nc = mg_bind_opt(&mgr, sslport, ev_handler, bind_opts); + if (nc == NULL) { + fprintf(stderr, "Error starting server on port %s: %s\n", sslport, err); + return EXIT_FAILURE; + } + } + else { + nc = mg_bind(&mgr, webport, ev_handler); + if (nc == NULL) { + fprintf(stderr, "Error starting server on port %s", webport ); + return EXIT_FAILURE; + } } if(run_as_user != NULL) { @@ -210,11 +271,18 @@ int main(int argc, char **argv) mg_mgr_free(&mgr); return EXIT_FAILURE; } - + + if (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"; - printf("myMPD started on port %s\n", webport); + printf("myMPD started on http port %s\n", webport); + if (ssl == true) + printf("myMPD started on ssl port %s\n", sslport); + while (s_signal_received == 0) { mg_mgr_poll(&mgr, 200); current_timer = time(NULL); From 2d58da89a2bfda22bd10f8b1ce207211dda28191 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Mon, 9 Jul 2018 23:51:02 +0100 Subject: [PATCH 02/22] =?UTF-8?q?First=20code=20f=C3=BCr=20pwa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- htdocs/assets/appicon-167.png | Bin 0 -> 2387 bytes htdocs/assets/appicon-192.png | Bin 0 -> 2789 bytes htdocs/assets/appicon-512.png | Bin 0 -> 8203 bytes htdocs/assets/appicon.png | Bin 6489 -> 0 bytes htdocs/index.html | 14 ++++++++------ htdocs/mympd.webmanifest | 21 +++++++++++++++++++++ 6 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 htdocs/assets/appicon-167.png create mode 100644 htdocs/assets/appicon-192.png create mode 100644 htdocs/assets/appicon-512.png delete mode 100644 htdocs/assets/appicon.png create mode 100644 htdocs/mympd.webmanifest diff --git a/htdocs/assets/appicon-167.png b/htdocs/assets/appicon-167.png new file mode 100644 index 0000000000000000000000000000000000000000..b58e6c81e7126c90b8b8e38796be1d09137354e0 GIT binary patch literal 2387 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 FV1ndpr`iAj literal 0 HcmV?d00001 diff --git a/htdocs/assets/appicon-192.png b/htdocs/assets/appicon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b6b7e7e64b2722174170517bb2dc7bedf747f0dc GIT binary patch literal 2789 zcmVeSaefwW^{L9a%BK_cXuvnZfkR6VQ^(G zZ*pgw?mQX*019PEL_t(|+U=eHS5(Ip!2O?Qm)$ikunSoEsyQhx3z9U)#DId)8d|N2 zS>s}iVve*qvM3c#YXFs4w0mfesg3r;a}bFTD?|_zl3=37ASNgaF4G@-=e@V@&DXa} z`|i(gXYOa;o0&WJ-np|3Epb`q05|{+fCJzFH~UKt>EiElA z(df>)s$$<#0dTKri1iIW#9CQ=INTR&SnbYefV{08pFPEddaZ2)t^9 z(CJnK;QPD2ud!MrC>!tSTkZ3y0Pqj$E&8a0EpFBUy+J$Rmk*FTtn21tzMNfQphwsA zt)Ogx{M*_-?$^uPnZNl!H?if*1;`oE_V5p>zJ=Vs=|)#TCV>B@ruk*xYwh`&6%a0jgHF!h-jRg zAE7qGJ6KijW@S zPhEllN63%zFj6iD$R3-;04mA-;|ItFIZ6(L3K0Mwxp(~wvPI65du^WpKs&j9dF%o3 z4iMcQ;|Hi9{6F{dkWCVpBHU!789_D*eb*)c**_5W zKeAOW!oDBnZ~^?8*nb7>1qcuZKnD*%39$s!Bb#Llu^1F_0QABZ;>dO(HX%Li058J$ z(R7~u00q$7vQWkh@K^W;L{S=qFp7Fv0Se(7Hj$MM0QWe21%-?NCm}wa|1g>@nV=5p zaDolM554#Obp4HU2kKJEb)du(KNG-y2>&W20LG)QNdD++0l45qbebN(GsEVaAptP{_phXF z@qr>gW>EudhNJ%lb^yE>UnvJ*Ih+kQQUhE7zv@C902n`w$;q=kW3?qW0?2}2P?!$@ z-0F1A!mNpMhuh1PmDF#1z#}WW~<%5a= z@O&qYxtkAu@vbGn``~H6Y5mU043n*gQ@`f%X!V|!lk&O z=>!`ZZ~C{CPg>M&DVP4HzE7oFWsoCt(D$6ptrE**$JS9s*W&I>a4j}6x)#@f=esk( zxqBA0QcB}qpZ)LiUu1%N9ke^^q6u((2IhC){LCeg8 zQe6@N1cQ7%<{>)+fFLj?z7_@$4zq&uamgGi05l5v|D_m!c%VB3qYH=*03LgYi2%q* zHLy~~2w*+<%~t{d{W6k0CP9O*Py!I?HkG^pa?)*}vwWI1-G<2f?&brKmGE@dDg)dQ z0J_+J%AAz58fpN~H2B%u>;P(#{vNrh0f-kj=3)epnRa={a#1w~fSGD|6BEEQnTfA3 zeKJM%UW(M?QTE(#6{*M9Ez-4812jSO*Qfy`Cx5>|aR?&?z>3G%Y$t6DAU*va)*_f; zOTQ=2(Ql;cPWk}|Vg$4!Hh@+0X+MIJ1xsNie!&Ppo{4=o9RSdyh|eKbfHHUr=y8Gl z`mcnG@O&vV0Q!iiQDHy8$Jp}hfH^=haU^!L-TH4Ojv^Lv0FWoK>^=;d_mE}^G6(Pz zZCtV!;1l9FZZ;PH+DmLu-mz2v{nQmoOpl}!RfXoN?9=~cV*Cy99!W^dnk&8?a{bA)s19VnyCurYsiuxwbIwKI!(Swpirw3M3D}pj_uA zsXLs!Pjm%GrIzb7Dwz6Ui^Tn-?%J18+F>SAvi3@$9Q6z0`fOP0dYTMHg9r36$O95Q zUY(hr(C+bA?uQw>e9%oo+gbQa6*NCXUe!!@3Hp{kyt#o;#QR(vj zO`Q82SN(N^=H{4^lvs<$S4|nE6O2VTQkh|a^!65xtHqq z<7?Tq;krbmE%kjS$+7O;;S{r=x3clfceZ&Xo7)FxEs8P>9t06k{*Cz2ZDrD9Jj$;3EehA}h(?dJv>b^>chrUp75P)03zi)K r2fzVv02}}ZzyWXo8~_L4{}|vug4qu6Kq%)E00000NkvXXu0mjfE1L{9 literal 0 HcmV?d00001 diff --git a/htdocs/assets/appicon-512.png b/htdocs/assets/appicon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..67f664aca037f6e33c486daa09838294ba8d3945 GIT binary patch literal 8203 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 z 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 d7Y3^VO + myMPD - myMPD + + + - - - - + + +
@@ -582,7 +584,7 @@
- +
diff --git a/htdocs/js/mpd.js b/htdocs/js/mpd.js index 1a98608..05e3544 100644 --- a/htdocs/js/mpd.js +++ b/htdocs/js/mpd.js @@ -756,8 +756,13 @@ function parseState(obj) { function getQueue() { if (app.current.search.length >= 2) sendAPI({"cmd": "MPD_API_SEARCH_QUEUE", "data": {"mpdtag":app.current.filter, "offset":app.current.page, "searchstr": app.current.search}}, parseQueue); - else - sendAPI({"cmd": "MPD_API_GET_QUEUE", "data": {"offset": app.current.page}}, parseQueue); + else { + var queue_version = document.getElementById('QueueList').getAttribute('data-version'); + if (last_state && queue_version != last_state.data.queue_version) + sendAPI({"cmd": "MPD_API_GET_QUEUE", "data": {"offset": app.current.page}}, parseQueue); + else + document.getElementById('QueueList').classList.remove('opacity05'); + } } function parseQueue(obj) { @@ -772,7 +777,9 @@ function parseQueue(obj) { document.getElementById('panel-heading-queue').innerText = ''; var nrItems = obj.data.length; - var tbody = document.getElementById(app.current.app + 'List').getElementsByTagName('tbody')[0]; + var table = document.getElementById(app.current.app + 'List'); + table.setAttribute('data-version', obj.queue_version); + var tbody = table.getElementsByTagName('tbody')[0]; var tr = tbody.getElementsByTagName('tr'); for (var i = 0; i < nrItems; i ++) { if (tr[i]) diff --git a/src/mpd_client.c b/src/mpd_client.c index 8d874f0..3581915 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -798,11 +798,12 @@ int mympd_put_queue(char *buffer, unsigned int offset) { mpd_entity_free(entity); } - len += json_printf(&out, "],totalTime: %d, totalEntities: %d, offset: %d, returnedEntities: %d }", + len += json_printf(&out, "],totalTime: %d, totalEntities: %d, offset: %d, returnedEntities: %d, queue_version: %d }", totalTime, entity_count, offset, - entities_returned + entities_returned, + mpd.queue_version ); if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); From 3c1dfe82f6c893450a4030cafe1dd6c5246d52c3 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 22:59:03 +0100 Subject: [PATCH 12/22] fix: cleanup about dialog --- htdocs/index.html | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/htdocs/index.html b/htdocs/index.html index 97c450a..d34c445 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -591,24 +591,19 @@
  • Homepage: https://github.com/jcorporation/mympd
  • Autor: Jürgen Mang <mail@jcgames.de>
  • -
    -
    Database Statistics
    + - -
    Database Statistics
    Artists
    Albums
    Songs
    DB Play Time
    DB Updated
    -
    -
    Play Statistics
    - - - + + +
    MPD Version
    Play Statistics
    Uptime
    Play Time
    MPD
    Protocol Version
    From cf34f414674d4e9132b76facca3ca7dab5360b3c Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 23:26:59 +0100 Subject: [PATCH 13/22] cleanup: renamed startup options cleanup: central startup options file: /etc/mympd/options --- CMakeLists.txt | 2 +- README.md | 12 ++++++------ contrib/mympd.service | 12 ++---------- contrib/{mympd.default => options} | 5 +++-- htdocs/js/mpd.min.js | 15 ++++++++------- mkrelease.sh | 7 ++----- mympd.1 | 10 +++++----- src/mympd.c | 10 +++++----- 8 files changed, 32 insertions(+), 41 deletions(-) rename contrib/{mympd.default => options} (88%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fabe703..2e7cffe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,4 +50,4 @@ install(FILES htdocs/css/bootstrap.min.css DESTINATION share/${PROJECT_NAME}/htd install(FILES htdocs/css/mpd.min.css DESTINATION share/${PROJECT_NAME}/htdocs/css/) install(DIRECTORY htdocs/assets DESTINATION share/${PROJECT_NAME}/htdocs) install(DIRECTORY DESTINATION /var/lib/${PROJECT_NAME}/) -install(DIRECTORY DESTINATION /etc/${PROJECT_NAME}/) +install(FILES contrib/options DESTINATION /etc/${PROJECT_NAME}/) diff --git a/README.md b/README.md index 93c61cc..8439543 100644 --- a/README.md +++ b/README.md @@ -56,16 +56,16 @@ Run flags ``` Usage: ./mympd [OPTION]... - -h, --host connect to mpd at host [localhost] - -p, --port connect to mpd at port [6600] + -h, --mpdhost connect to mpd at host [localhost] + -p, --mpdport connect to mpd at port [6600] + -m, --mpdpass specifies the password to use when connecting to mpd -w, --webport listen port for webserver [80] -S, --ssl enable ssl - -W, --sslport listen port for ssl webserver [443] - -C, --sslcert filename for ssl certificate [/etc/mympd/ssl/server.pem] - -K, --sslkey filename for ssl key [/etc/mympd/ssl/server.key] + -W, --sslport listen port for ssl webserver [443] + -C, --sslcert filename for ssl certificate [/etc/mympd/ssl/server.pem] + -K, --sslkey filename for ssl key [/etc/mympd/ssl/server.key] -s, --streamport connect to mpd http stream at port [8000] -u, --user drop priviliges to user after socket bind - -m, --mpdpass specifies the password to use when connecting to mpd -i, --coverimage filename for coverimage [folder.jpg] -t, --statefile filename for mympd state [/var/lib/mympd/mympd.state] -v, --version get version diff --git a/contrib/mympd.service b/contrib/mympd.service index a20b865..f8afeb7 100644 --- a/contrib/mympd.service +++ b/contrib/mympd.service @@ -3,16 +3,8 @@ Description=myMPD server daemon Requires=network.target local-fs.target [Service] -Environment=MPD_HOST=localhost -Environment=MPD_PORT=6600 -Environment=MPD_PASSWORD= -Environment=WEB_PORT=80 -Environment=MYMPD_USER=nobody -Environment=COVERIMAGE=--coverimage folder.jpg -Environment=STATEFILE=--statefile /var/lib/mympd/mympd.state -#Environment=SSL=-S -W 443 -C /etc/mympd/ssl/server.pem -K /etc/mympd/ssl/server.key -EnvironmentFile=/etc/default/mympd -ExecStart=/usr/bin/mympd --user $MYMPD_USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $COVERIMAGE $STATEFILE $SSL +EnvironmentFile=/etc/mympd/options +ExecStart=/usr/bin/mympd --user $MYMPD_USER --webport $WEB_PORT --mpdhost $MPD_HOST --mpdport $MPD_PORT $MPD_PASSWORD $COVERIMAGE $STATEFILE $SSL Type=simple [Install] diff --git a/contrib/mympd.default b/contrib/options similarity index 88% rename from contrib/mympd.default rename to contrib/options index d3217ee..cdf3662 100644 --- a/contrib/mympd.default +++ b/contrib/options @@ -1,9 +1,10 @@ #Copy this file to /etc/default/mympd MPD_HOST=localhost MPD_PORT=6600 -MPD_PASSWORD= +#MPD_PASSWORD=--mpdpass PASSWORD WEB_PORT=80 +#SSL=-S -W 443 -C /etc/mympd/ssl/server.pem -K /etc/mympd/ssl/server.key MYMPD_USER=nobody COVERIMAGE=--coverimage folder.jpg STATEFILE=--statefile /var/lib/mympd/mympd.state -#SSL=-S -W 443 -C /etc/mympd/ssl/server.pem -K /etc/mympd/ssl/server.key + diff --git a/htdocs/js/mpd.min.js b/htdocs/js/mpd.min.js index 1e6d797..61d3503 100644 --- a/htdocs/js/mpd.min.js +++ b/htdocs/js/mpd.min.js @@ -50,11 +50,12 @@ function parseState(a){if(JSON.stringify(a)!==JSON.stringify(last_state)){1==a.d 0==a.data.queue_length?domCache.btnPlay.setAttribute("disabled","disabled"):domCache.btnPlay.removeAttribute("disabled");-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.volumeIcon.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume;current_song.totalTime= a.data.totalTime;current_song.currentSongId=a.data.currentsongid;var b=Math.floor(a.data.totalTime/60),c=a.data.totalTime-60*b,e=Math.floor(a.data.elapsedTime/60),d=a.data.elapsedTime-60*e;domCache.progressBar.value=Math.floor(100*a.data.elapsedTime/a.data.totalTime);b=e+":"+(10>d?"0":"")+d+" / "+b+":"+(10>c?"0":"")+c;domCache.counter.innerText=b;last_state&&(c=document.getElementById("queueTrackId"+last_state.data.currentsongid))&&(e=c.getElementsByTagName("td"),e[4].innerText=c.getAttribute("data-duration"), e[0].classList.remove("material-icons"),e[0].innerText=c.getAttribute("data-songpos"),c.classList.remove("font-weight-bold"));if(c=document.getElementById("queueTrackId"+a.data.currentsongid))e=c.getElementsByTagName("td"),e[4].innerText=b,e[0].classList.add("material-icons"),e[0].innerText="play_arrow",c.classList.add("font-weight-bold");void 0!=last_state&&a.data.queue_version==last_state.data.queue_version||sendAPI({cmd:"MPD_API_GET_CURRENT_SONG"},songChange);b=a.data.outputs.length;for(c=0;c< -b;c++)toggleBtn("btnoutput"+a.data.outputs[c].id,a.data.outputs[c].state);last_state=a}}function getQueue(){2<=app.current.search.length?sendAPI({cmd:"MPD_API_SEARCH_QUEUE",data:{mpdtag:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue):sendAPI({cmd:"MPD_API_GET_QUEUE",data:{offset:app.current.page}},parseQueue)} -function parseQueue(a){if("Queue"===app.current.app){0g?"0":"")+g;g=document.createElement("tr");g.setAttribute("data-trackid",a.data[d].id);g.setAttribute("id","queueTrackId"+a.data[d].id);g.setAttribute("data-songpos",a.data[d].pos+1);g.setAttribute("data-duration",f);g.setAttribute("data-uri",a.data[d].uri);g.innerHTML=""+(a.data[d].pos+1)+""+a.data[d].title+""+a.data[d].artist+""+a.data[d].album+ -""+f+'playlist_add';d=b;d--)e[d].remove();"queuesearch"==a.type&&0==b?c.innerHTML='error_outlineNo results, please refine your search!':"queue"==a.type&&0==b&&(c.innerHTML='error_outlineEmpty queue');setPagination(a.totalEntities); -document.getElementById("QueueList").classList.remove("opacity05")}}function parseSearch(a){"Search"===app.current.app&&(document.getElementById("panel-heading-search").innerHTML=a.totalEntities+" Songs found",0g?"0":"")+g;g=document.createElement("tr");g.setAttribute("data-trackid",a.data[d].id);g.setAttribute("id","queueTrackId"+a.data[d].id);g.setAttribute("data-songpos",a.data[d].pos+1);g.setAttribute("data-duration",f);g.setAttribute("data-uri",a.data[d].uri);g.innerHTML=""+(a.data[d].pos+1)+""+a.data[d].title+ +""+a.data[d].artist+""+a.data[d].album+""+f+'playlist_add';d=b;d--)e[d].remove();"queuesearch"==a.type&&0==b?c.innerHTML='error_outlineNo results, please refine your search!':"queue"==a.type&&0==b&&(c.innerHTML='error_outlineEmpty queue'); +setPagination(a.totalEntities);document.getElementById("QueueList").classList.remove("opacity05")}}function parseSearch(a){"Search"===app.current.app&&(document.getElementById("panel-heading-search").innerHTML=a.totalEntities+" Songs found",0playlist_add';break;case "song":f=Math.floor(a.data[d].duration/60);var h=a.data[d].duration-60*f;g.innerHTML='music_note'+a.data[d].title+""+a.data[d].artist+""+a.data[d].album+""+f+":"+(10>h?"0":"")+h+'playlist_add'; break;case "plist":g.innerHTML='list'+a.data[d].name+'playlist_add'}d=b;d--)e[d].remove();setPagination(a.totalEntities);0==b&&(c.innerHTML='error_outlineNo results');document.getElementById(app.current.app+(void 0==app.current.tab?"": @@ -94,8 +95,8 @@ mixrampdb:document.getElementById("inputMixrampdb").value,mixrampdelay:document. function addAllFromBrowse(){sendAPI({cmd:"MPD_API_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}function addAllFromSearch(){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_SEARCH_ADD",data:{filter:app.current.filter,searchstr:app.current.search}}),showNotification("Added "+parseInt(document.getElementById("panel-heading-search").innerText)+" songs from search","","","success"))} function scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a}function gotoPage(a){switch(a){case "next":app.current.page+=settings.max_elements_per_page;break;case "prev":app.current.page-=settings.max_elements_per_page;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 addStream(){var a=document.getElementById("streamurl");""!=a.value&&sendAPI({cmd:"MPD_API_ADD_TRACK",data:{uri:a.value}});a.value="";modalAddstream.hide()}function saveQueue(){var a=document.getElementById("playlistname");""!=a.value&&sendAPI({cmd:"MPD_API_SAVE_QUEUE",data:{plist:a.value}});a.value="";modalSavequeue.hide()} -function showNotification(a,b,c,e){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.classList.add("alert","alert-"+e),b.innerHTML="
    "+a+""+c+"
    ",document.getElementsByTagName("main")[0].append(b),document.getElementById("alertBox").classList.add("alertBoxActive"), -alertTimeout&&clearTimeout(alertTimeout),alertTimeout=setTimeout(function(){document.getElementById("alertBox")&&document.getElementById("alertBox").classList.remove("alertBoxActive");setTimeout(function(){document.getElementById("alertBox").remove()},600)},3E3))}function notificationsSupported(){return"Notification"in window} +function showNotification(a,b,c,e){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.add("alert","alert-"+e),b.innerHTML="
    "+a+""+c+"
    ",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(){document.getElementById("alertBox").remove()},600))}function notificationsSupported(){return"Notification"in window} function songChange(a){var b=a.data.title+a.data.artist+a.data.album+a.data.uri+a.data.currentsongid;if(last_song!=b){var c="",e="",d="myMPD: ";document.getElementById("album-cover").style.backgroundImage='url("'+a.data.cover+'")';"undefined"!=typeof a.data.artist&&0"+a.data.artist,d+=a.data.artist+" - ",document.getElementById("artist").innerText=a.data.artist):document.getElementById("artist").innerText="";"undefined"!=typeof a.data.album&& 0 Date: Sun, 15 Jul 2018 23:30:50 +0100 Subject: [PATCH 14/22] cleanup: update favicon --- htdocs/assets/favicon.ico | Bin 4286 -> 2238 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/htdocs/assets/favicon.ico b/htdocs/assets/favicon.ico index 01108ee5dfbfe70664d46b72ed10a39e18be7fc0..4c19d1ba663b473ae373be57f4aee5dc60b8d35f 100644 GIT binary patch literal 2238 zcmeHINi$qg7(H4{AuR(5iB3df2ogzpq&s=KTWg_7>oMK^@>v)brk`MkHH9iW8^prK zu(CB2%1V_gGfOLFDn%LcUia&X@&i(;$JNA5Se8Xi zO$};mYf)EMhx+<@G&D4zv9S?NO-*QSZbnN>3tC%S(bm?6_V#vkbabGzvlCriUFh!a zMo&)Hiq%>aZF51U~+O2Q&Urz zo}R|c%nW8{XE8T7hxz$=EG#Twad8n#OG{W@UdGDG3RYKFv9`8`_4RdZY;0h2a}!%z zTiD*-#?HOo5%qS-~(BP9odqm>Y_u zDq_`HQ9K%*tzpUZB218BC4EW|Ni~r5ebB{o3#lH3_z+3SniK$Ht4JPj1Vc%cffx!% zA9<6HG|(3Ti?_4Bvr|POs@JSdNHdSrK3ro=4-sf6*XJ>mCGhn3$FqPUhkkl(k<01L z8_;mo`}5r&R0SH(A~Da4+)kmk;qgTY@j*);c^w~rXnxuLbMvWC;KS;p_SYxD;o<*iKv;cKSbMy0 z8NQM(wDMjwvV;l*8gXWl`ECEKpx?otVzQM~VI2;?AHWA2>0bGl0x2bA%?P@jm24(Y zQKq*~QJHF%s)D!6r;GdMAaay5OgT5(3xa0{wn@m>y7~UOi}U=61vtSFB}ycTw^F^S Hx>x>x8e8zH literal 4286 zcmb`JOKTKC6oo4&48o8IJ{G!|cBQKXTo`w{jKknk@bM3fYuybvLO=&W7iRB5B!3_- zGl-%gj!z=)CjY?YfRgrnp$jf$Rd;7*Za7KxeVo&M`&M$DKRvoVy^% z<*o|&XJk$BoDr6Uj&QpGYkb7WnYEo0VOQ7?-V5J_Z^G9Ctnm>ehuln<*5*af6MhJv zgq@loml|r}n8eyDdL!^`b#=o$YBvSA4r6zeye))$_L^rL7O3hCTyWMg@^%HQ#hQ2h zL0A{=3M;~L0oM44kwb1+4`LCj=}r`14nQRoPE?%`Y! z%FOF~zFj5163&+t!3npoK@)Ahp>{Tgy5?Lfvrr#SxY=v>g*hQKIN|pCX!`H>EcD?X z2&p}r(x(U0_WzeS`U3NmvTF*&`X8IW)f7qL_x*?Ot#G{}pD>&|27dlx`)^%&Rr`;b z<32b$s|+qUt-jxXP5Hy~=c9al$5QrOfg3_>&SEvE1e`}2qV*!kNhkg#J3f65J) zopIiqKYyvogxL3o{V}caKS$N~2MxZ7b|26y-qB@YQll0Q+jle`MhNHS+YEB)6ZKX9 zH!0;mqlQ|$=eQ3v(27}G&TYZoFKhbO7nq~6eL;*Ia>Jg(12^YDnQ`CjE%GgxA7+?+ zv?{=}T!1w`V&vFa^Bi#2?IU@yK6ozv($`^uu*Wu!HC%PIm3m-p{pMYTGZ}K1T~iAO z{h7p=6ZQprv+5b`eUM8HbDo+tg=$--OwNl9ftlv)=Gl19fHgj1q!{PHoGvQ6tZt%lCEPf1SHBZj4<jPaMcfYZf=SK#4-UGcq`Jgw>2W#W* e-rBe&EEHhf-6KYh&!-j+xZs2v4YUq4CH)Vv!>~U9 From b8141360ddb1ef4d7055a638609f2ef2312f9776 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 23:33:06 +0100 Subject: [PATCH 15/22] cleanup: startup options --- contrib/options | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/options b/contrib/options index cdf3662..a069243 100644 --- a/contrib/options +++ b/contrib/options @@ -1,4 +1,4 @@ -#Copy this file to /etc/default/mympd +#myMPD startup options MPD_HOST=localhost MPD_PORT=6600 #MPD_PASSWORD=--mpdpass PASSWORD @@ -7,4 +7,3 @@ WEB_PORT=80 MYMPD_USER=nobody COVERIMAGE=--coverimage folder.jpg STATEFILE=--statefile /var/lib/mympd/mympd.state - From 0df60f04f023a5f95078d782889c0caebb93a272 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 23:41:16 +0100 Subject: [PATCH 16/22] fix: options description --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8439543..d071a94 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ 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. @@ -46,10 +46,10 @@ Dependencies Unix Build Instructions ----------------------- -1. install dependencies. cmake, libmpdclient (dev) and OpenSSL (dev) are available from all major distributions. -2. build and install it ```cd /path/to/src; ./mkrelease.sh``` -3. Link your mpd music directory to ```/usr/share/mympd/htdocs/library``` and put ```folder.jpg``` files in your album directories (mkrelease.sh tries to do this for you) -4. Configure your mpd with http stream output to use the local player +1. Install dependencies: cmake, libmpdclient (dev) and OpenSSL (dev) are available from all major distributions. +2. Build and install: ```cd /path/to/src; ./mkrelease.sh```. +3. Link your mpd music directory to ```/usr/share/mympd/htdocs/library``` and put ```folder.jpg``` files in your album directories (mkrelease.sh tries to do this for you). +4. Configure your mpd with http stream output to use the local player. Run flags --------- @@ -75,9 +75,10 @@ Usage: ./mympd [OPTION]... SSL --- -1. Create ca and certificate ```/path/to/src/contrib/crcert.sh``` (mkrelease.sh do this for you) -2. Start mympd with ```-S``` (use default certificate under ```/etc/mympd/ssl/```) -3. Import ```/etc/mympd/ssl/ca/ca.pem``` in your browser to trust the certificate +1. Create ca and certificate ```/path/to/src/contrib/crcert.sh``` (mkrelease.sh do this for you). +2. Start mympd with ```-S``` (use default certificate under ```/etc/mympd/ssl/```). +3. Import ```/etc/mympd/ssl/ca/ca.pem``` in your browser to trust the certificate. +4. myMPD redirects http requests to https, ensure that myMPD hostname is resolvable. Copyright --------- From 999c73fa75bd0f35617feee86656221d5a53dcb8 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 23:43:34 +0100 Subject: [PATCH 17/22] cleanup: typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d071a94..c435f0e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ SSL --- 1. Create ca and certificate ```/path/to/src/contrib/crcert.sh``` (mkrelease.sh do this for you). -2. Start mympd with ```-S``` (use default certificate under ```/etc/mympd/ssl/```). +2. Start myMPD with ```-S``` (use default certificate under ```/etc/mympd/ssl/```). 3. Import ```/etc/mympd/ssl/ca/ca.pem``` in your browser to trust the certificate. 4. myMPD redirects http requests to https, ensure that myMPD hostname is resolvable. From ed2277ddc2cd70c14d7ce7c0df56284296897625 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 23:45:10 +0100 Subject: [PATCH 18/22] cleanup: move manpage to contrib directory --- CMakeLists.txt | 2 +- mympd.1 => contrib/mympd.1 | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename mympd.1 => contrib/mympd.1 (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e7cffe..5a801cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ add_executable(mympd ${SOURCES}) target_link_libraries(mympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES}) install(TARGETS mympd DESTINATION bin) -install(FILES mympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1) +install(FILES contrib/mympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1) install(FILES htdocs/index.html DESTINATION share/${PROJECT_NAME}/htdocs/) install(FILES htdocs/player.html DESTINATION share/${PROJECT_NAME}/htdocs/) install(FILES htdocs/js/player.min.js DESTINATION share/${PROJECT_NAME}/htdocs/js/) diff --git a/mympd.1 b/contrib/mympd.1 similarity index 100% rename from mympd.1 rename to contrib/mympd.1 From 9b71a5e6ae93b08344a311171a6196c69db4130c Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sun, 15 Jul 2018 23:51:36 +0100 Subject: [PATCH 19/22] cleanup: formatting --- src/mpd_client.c | 2 +- src/mympd.c | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/mpd_client.c b/src/mpd_client.c index 3581915..569e343 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -80,7 +80,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) case MPD_API_SET_SETTINGS: json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b); char tmpfile[200]; - snprintf(tmpfile,200,"%s.tmp",mpd.statefile); + snprintf(tmpfile,200,"%s.tmp", mpd.statefile); json_fprintf(tmpfile, "{ notificationWeb: %d, notificationPage: %d}", state.a, state.b); rename(tmpfile,mpd.statefile); diff --git a/src/mympd.c b/src/mympd.c index 7f5f761..50cf2ad 100644 --- a/src/mympd.c +++ b/src/mympd.c @@ -121,7 +121,7 @@ int main(int argc, char **argv) { strcpy(mpd.host, "127.0.0.1"); streamport = 8000; strcpy(coverimage, "folder.jpg"); - mpd.statefile="/var/lib/mympd/mympd.state"; + mpd.statefile = "/var/lib/mympd/mympd.state"; struct mg_bind_opts bind_opts; const char *err; bool ssl = false; @@ -192,7 +192,7 @@ int main(int argc, char **argv) { case 'v': fprintf(stdout, "myMPD %d.%d.%d\n" "Copyright (C) 2018 Juergen Mang \n" - "Built " __DATE__ " "__TIME__ "\n", + "Built " __DATE__ " "__TIME__"\n", MYMPD_VERSION_MAJOR, MYMPD_VERSION_MINOR, MYMPD_VERSION_PATCH); return EXIT_SUCCESS; break; @@ -229,7 +229,7 @@ int main(int argc, char **argv) { snprintf(s_redirect, 200, "https://%s:%s/", hostname, sslport); nc_http = mg_bind(&mgr, webport, ev_handler_http); if (nc_http == NULL) { - fprintf(stderr, "Error starting server on port %s", webport ); + fprintf(stderr, "Error starting server on port %s\n", webport ); return EXIT_FAILURE; } memset(&bind_opts, 0, sizeof(bind_opts)); @@ -245,7 +245,7 @@ int main(int argc, char **argv) { else { nc = mg_bind(&mgr, webport, ev_handler); if (nc == NULL) { - fprintf(stderr, "Error starting server on port %s", webport ); + fprintf(stderr, "Error starting server on port %s\n", webport ); return EXIT_FAILURE; } } @@ -257,7 +257,7 @@ int main(int argc, char **argv) { printf("Unknown user\n"); return EXIT_FAILURE; } else if (setgid(pw->pw_gid) != 0) { - printf("setgid() failed"); + printf("setgid() failed\n"); return EXIT_FAILURE; } else if (setuid(pw->pw_uid) != 0) { printf("setuid() failed\n"); @@ -285,8 +285,7 @@ int main(int argc, char **argv) { while (s_signal_received == 0) { mg_mgr_poll(&mgr, 200); current_timer = time(NULL); - if(current_timer - last_timer) - { + if (current_timer - last_timer) { last_timer = current_timer; mympd_poll(&mgr); } From 02c9a5635e20af9f054d192f8340e920e993df4b Mon Sep 17 00:00:00 2001 From: jcorporation Date: Mon, 16 Jul 2018 00:56:58 +0100 Subject: [PATCH 20/22] cleanup: remove search from main menu cleanup: coding style --- htdocs/css/mpd.css | 4 --- htdocs/index.html | 12 +++---- htdocs/js/mpd.js | 82 ++++++++++++++++++---------------------------- 3 files changed, 35 insertions(+), 63 deletions(-) diff --git a/htdocs/css/mpd.css b/htdocs/css/mpd.css index 7d898e7..de7201a 100644 --- a/htdocs/css/mpd.css +++ b/htdocs/css/mpd.css @@ -36,10 +36,6 @@ button { min-width: 50px; } -#search { - width: 200px; -} - .card { min-height:350px; } diff --git a/htdocs/index.html b/htdocs/index.html index d34c445..9f8ffaa 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -23,10 +23,6 @@ myMPD