diff --git a/CMakeLists.txt b/CMakeLists.txt index 16101ca..f8ed44a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ 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 "4") +set(CPACK_PACKAGE_VERSION_MINOR "5") set(CPACK_PACKAGE_VERSION_PATCH "0") if(CMAKE_BUILD_TYPE MATCHES RELEASE) @@ -34,6 +34,7 @@ set(SOURCES src/mpd_client.c dist/src/mongoose/mongoose.c dist/src/frozen/frozen.c + dist/src/inih/ini.c ) add_executable(mympd ${SOURCES}) @@ -52,4 +53,4 @@ install(FILES dist/htdocs/css/bootstrap.min.css DESTINATION share/${PROJECT_NAME install(FILES dist/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(FILES contrib/options DESTINATION /etc/${PROJECT_NAME}/) +install(FILES contrib/mympd.conf DESTINATION /etc/${PROJECT_NAME}/) diff --git a/README.md b/README.md index 83a6858..ec4d58b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Backend ------- - Mongoose: https://github.com/cesanta/mongoose - Frozen: https://github.com/cesanta/frozen + - inih: https://github.com/benhoyt/inih Dependencies ------------ @@ -68,29 +69,14 @@ Unix Build Instructions Run Flags --------- ``` -Usage: ./mympd [OPTION]... - - -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] - -s, --streamport connect to mpd http stream at port [8000] - -u, --user drop priviliges to user after socket bind - -i, --coverimage filename for coverimage [folder.jpg] - -t, --statefile filename for mympd state [/var/lib/mympd/mympd.state] - -v, --version get version - --help this help +Usage: ./mympd /etc/mypd/mympd.conf ``` 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/```). +1. Create ca and certificate ```/path/to/src/contrib/crcert.sh``` (mkrelease.sh does this for you). +2. Set ```ssl=true``` in /etc/mympd/mympd.conf (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. diff --git a/contrib/mympd.1 b/contrib/mympd.1 index 6e8940b..f0b6968 100644 --- a/contrib/mympd.1 +++ b/contrib/mympd.1 @@ -1,61 +1,18 @@ .\" Manpage for myMPD. .\" Contact mail@jcgames.de to correct errors or typos. -.TH man 1 "24 May 2018" "1.0.0" "myMPD man page" +.TH man 1 "06 Aug 2018" "3.5.0" "myMPD man page" .SH NAME myMPD \- Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS .SH SYNOPSIS -mympd [OPTION]... +mympd /path/to/mympd.conf .SH DESCRIPTION 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. -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-mpdhost HOST\fR -connect to mpd at host, defaults to localhost -.TP -\fB\-p\fR, \fB\-\-mpdport PORT\fR -connect to mpd at port, defaults to 6600 -.TP -\fB\-m\fR, \fB\-\-mpdpass PASSWORD\fR -specifies the password to use when connecting to mpd -.TP -\fB\-w\fR, \fB\-\-webport PORT\fR -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/ssl/server.pem] -.TP -\fB\-K\fR, \fB\-\-sslkey FILENAME\fR -filename for ssl key [/etc/mympd/ssl/server.key] -.TP -\fB-s\fR, \fB\-\-streamport PORT -connect to mpd http stream at port [8000] -.TP -\fB\-u\fR, \fB\-\-user USERNAME\fR -drop privileges to the provided username after socket binding -.TP -\fB-i\fR, \fB\-\-coverimage FILENAME\fR -filename for coverimage [folder.jpg] -.TP -\fB-t\fR, \fB\-\-statefile FILENAME\fR -filename for mympd state [/var/lib/mympd/mympd.state] -.TP -\fB\-v\fR, \fB\-\-version\fR -print version and exit -.TP -\fB\-\-help\fR -print all valid options and exits .SH BUGS No known bugs. .SH AUTHOR Juergen Mang (mail@jcgames.de) -https://github.com/jcorporation/ympd \ No newline at end of file +https://github.com/jcorporation/mympd \ No newline at end of file diff --git a/contrib/mympd.conf b/contrib/mympd.conf new file mode 100644 index 0000000..b1bc56e --- /dev/null +++ b/contrib/mympd.conf @@ -0,0 +1,27 @@ +#myMPD config file + +#Connection to mpd +mpdhost = 127.0.0.1 +mpdport = 6600 +#mpdpass = + +#Webserver options +webport = 80 + +#Enable ssl +ssl = true +sslport = 443 +sslcert = /etc/mympd/ssl/server.pem +sslkey = /etc/mympd/ssl/server.key + +#myMPD user +user = nobody + +#Port for mpd http stream +streamport = 8000 + +#Name for coverimages +coverimage = folder.jpg + +#myMPD statefile +statefile = /var/lib/mympd/mympd.state diff --git a/contrib/mympd.service b/contrib/mympd.service index 9b27106..8e9e0e6 100644 --- a/contrib/mympd.service +++ b/contrib/mympd.service @@ -3,8 +3,7 @@ Description=myMPD server daemon Requires=network.target local-fs.target mpd.service [Service] -EnvironmentFile=/etc/mympd/options -ExecStart=/usr/bin/mympd --user $MYMPD_USER --webport $WEB_PORT --mpdhost $MPD_HOST --mpdport $MPD_PORT $MPD_PASSWORD --coverimage $COVERIMAGE --statefile $STATEFILE $SSL +ExecStart=/usr/bin/mympd /etc/mympd/mympd.conf Type=simple [Install] diff --git a/contrib/options b/contrib/options deleted file mode 100644 index 1571abf..0000000 --- a/contrib/options +++ /dev/null @@ -1,9 +0,0 @@ -#myMPD startup options -MPD_HOST=localhost -MPD_PORT=6600 -#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=folder.jpg -STATEFILE=/var/lib/mympd/mympd.state diff --git a/dist/htdocs/index.html b/dist/htdocs/index.html index 50af297..9e70f59 100644 --- a/dist/htdocs/index.html +++ b/dist/htdocs/index.html @@ -1 +1 @@ -myMPD
Playback

  

#TitleArtistAlbumDuration
PlaylistLast modified
Playlist List
#TitleArtistAlbumDuration
Artist
TitleArtistAlbumDuration
Search
TitleArtistAlbumDuration
\ No newline at end of file +myMPD
Playback

  

#TitleArtistAlbumDuration
PlaylistLast modified
Playlist List
#TitleArtistAlbumDuration
Artist
TitleArtistAlbumDuration
Search
TitleArtistAlbumDuration
\ No newline at end of file diff --git a/dist/htdocs/js/mpd.min.js b/dist/htdocs/js/mpd.min.js index 7a190b8..17f6e49 100644 --- a/dist/htdocs/js/mpd.min.js +++ b/dist/htdocs/js/mpd.min.js @@ -8,8 +8,8 @@ $jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){va var socket,last_song="",last_state,current_song={},playstate="",settings={},alertTimeout,deferredPrompt,dragEl,app={apps:{Playback:{state:"0/-/",scrollPos:0},Queue:{state:"0/Any Tag/",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:"Artist",views:{Artist:{state:"0/-/",scrollPos:0},Album:{state:"0/-/",scrollPos:0}}}}},Search:{state:"0/Any Tag/",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.panelHeadingBrowse=document.getElementById("panel-heading-browse").getElementsByTagName("a");domCache.panelHeadingBrowseLen=domCache.panelHeadingBrowse.length; domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");domCache.volumeIcon=document.getElementById("volumeIcon");domCache.btnPlay=document.getElementById("btnPlay");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"); -var modalConnectionError=new Modal(document.getElementById("modalConnectionError")),modalSettings=new Modal(document.getElementById("modalSettings")),modalSavequeue=new Modal(document.getElementById("modalSaveQueue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),modalAddToPlaylist=new Modal(document.getElementById("modalAddToPlaylist")),modalRenamePlaylist=new Modal(document.getElementById("modalRenamePlaylist")),mainMenu=new Dropdown(document.getElementById("mainMenu")); +domCache.outputs=document.getElementById("outputs");domCache.btnAdd=document.getElementById("nav-add2homescreen");domCache.currentTrack=document.getElementById("currentTrack"); +var modalConnectionError=new Modal(document.getElementById("modalConnectionError")),modalSettings=new Modal(document.getElementById("modalSettings")),modalSavequeue=new Modal(document.getElementById("modalSaveQueue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),modalAddToPlaylist=new Modal(document.getElementById("modalAddToPlaylist")),modalRenamePlaylist=new Modal(document.getElementById("modalRenamePlaylist")); 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;bsearchSearching...'),2<=app.current.search.length?sendAPI({cmd:"MPD_API_SEARCH",data:{mpdtag:app.current.filter,offset:app.current.page,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("SearchList").classList.remove("opacity05"),setPagination(0)),b=document.getElementById("searchtags").getElementsByTagName("button"),c=b.length,a=0;avolume_up '+a.data.outputs[e].name+"";domCache.outputs.innerHTML=b} +function parseOutputnames(a){for(var b="",c=a.data.outputs.length,e=0;e";domCache.outputs.innerHTML=b} function parseState(a){if(JSON.stringify(a)!==JSON.stringify(last_state)){1==a.data.state?(domCache.btnPlay.innerText="play_arrow",playstate="stop"):2==a.data.state?(domCache.btnPlay.innerText="pause",playstate="play"):(domCache.btnPlay.innerText="play_arrow",playstate="pause");-1==a.data.nextsongpos?domCache.btnNext.setAttribute("disabled","disabled"):domCache.btnNext.removeAttribute("disabled");0>=a.data.songpos?domCache.btnPrev.setAttribute("disabled","disabled"):domCache.btnPrev.removeAttribute("disabled"); 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)} +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);"-1"==a.data.songpos&&(domCache.currentTrack.innerText= +"Not playing",document.getElementById("currentAlbum").innerText="",document.getElementById("currentArtist").innerText="",document.getElementById("currentCover").style.backgroundImage="");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("draggable","true");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&& @@ -75,11 +75,11 @@ document.getElementById("BrowsePlaylistsAllList").classList.add("hide"),document h.setAttribute("data-uri",f);h.setAttribute("data-type","plist");h.setAttribute("data-name",a.data[d].name);h.innerHTML='list'+a.data[d].name+""+g.toUTCString()+'playlist_add';d"+g+""+a.data[d].title+""+a.data[d].artist+""+a.data[d].album+""+f+":"+(10>k?"0":"")+k+'playlist_add'; d=b;d--)e[d].remove();setPagination(a.totalEntities);0==b&&(c.innerHTML="All"==app.current.view?'error_outlineNo playlists found.':'error_outlineEmpty playlist.');document.getElementById(app.current.app+app.current.tab+app.current.view+"List").classList.remove("opacity05")}} -function parseListDBtags(a){if("Browse"===app.current.app||"Database"===app.current.tab||"Artist"===app.current.view)if("AlbumArtist"==a.tagtype){document.getElementById("BrowseDatabaseAlbumCards").classList.add("hide");document.getElementById("BrowseDatabaseArtistList").classList.remove("hide");document.getElementById("btnBrowseDatabaseArtist").parentNode.classList.add("hide");for(var b=a.data.length,c=document.getElementById(app.current.app+app.current.tab+app.current.view+"List").getElementsByTagName("tbody")[0], +function parseListDBtags(a){if("Browse"===app.current.app||"Database"===app.current.tab||"Artist"===app.current.view)if("AlbumArtist"==a.tagtype){document.getElementById("BrowseDatabaseAlbumList").classList.add("hide");document.getElementById("BrowseDatabaseArtistList").classList.remove("hide");document.getElementById("btnBrowseDatabaseArtist").parentNode.classList.add("hide");for(var b=a.data.length,c=document.getElementById(app.current.app+app.current.tab+app.current.view+"List").getElementsByTagName("tbody")[0], e=c.getElementsByTagName("tr"),d=0;dalbum'+a.data[d].value+"";d=b;d--)e[d].remove();setPagination(a.totalEntities);0==b&&(c.innerHTML='error_outlineNo entries found.'); -document.getElementById("BrowseDatabaseArtistList").classList.remove("opacity05")}else if("Album"==a.tagtype){document.getElementById("BrowseDatabaseAlbumCards").classList.remove("hide");document.getElementById("BrowseDatabaseArtistList").classList.add("hide");document.getElementById("btnBrowseDatabaseArtist").parentNode.classList.remove("hide");b=a.data.length;c=document.getElementById("BrowseDatabaseAlbumCards");e=c.querySelectorAll(".col-md");for(d=0;d
'+a.searchstr+'

'+a.data[d].value+'

',d=b;d--)e[d].remove();setPagination(a.totalEntities);document.getElementById("BrowseDatabaseAlbumCards").classList.remove("opacity05")}} +data:{albumartist:a.searchstr,album:a.data[d].value}},parseListTitles));for(d=e.length-1;d>=b;d--)e[d].remove();setPagination(a.totalEntities);document.getElementById("BrowseDatabaseAlbumList").classList.remove("opacity05")}} function parseListTitles(a){if("Browse"===app.current.app||"Database"===app.current.tab||"Album"===app.current.view){var b=genId(a.album),c=document.getElementById("card"+b);b=c.getElementsByTagName("tbody")[0];var e=c.getElementsByTagName("img")[0];c=e.parentNode;e.setAttribute("src",a.cover);c.setAttribute("data-uri",encodeURI(a.data[0].uri.replace(/\/[^\/]+$/,"")));c.setAttribute("data-name",a.album);c.setAttribute("data-type","dir");e="";for(var d=a.data.length,f=0;f'+a.data[f].track+""+a.data[f].title+'playlist_add';b.innerHTML=e;c.addEventListener("click",function(a){a.preventDefault();showMenu(this)},!1);b.parentNode.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&& (a.preventDefault(),showMenu(a.target))},!1)}} @@ -88,8 +88,8 @@ function setPagination(a){var b=Math.ceil(a/settings.max_elements_per_page),c=ap document.getElementById(c+"ButtonsBottom").classList.add("hide"));0f?"0":"")+f):"uri"==d&&(f=''+f+"");b[e].getElementsByTagName("td")[1].innerHTML= f}}function playlistDetails(a){appGoto("Browse","Playlists","Detail","0/-/"+a)}function removeFromPlaylist(a,b){b--;sendAPI({cmd:"MPD_API_RM_PLAYLIST_TRACK",data:{uri:a,track:b}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05");sendAPI({cmd:"MPD_API_GET_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists)} function playlistClear(){var a=document.getElementById("BrowsePlaylistsDetailList").getAttribute("data-uri");sendAPI({cmd:"MPD_API_PLAYLIST_CLEAR",data:{uri:a}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05");sendAPI({cmd:"MPD_API_GET_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists)} @@ -125,9 +125,10 @@ function saveQueue(){var a=document.getElementById("saveQueueName").value,b=a.re 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.remove("alert-success","alert-danger"),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){if("error"!=a.type&&"result"!=a.type){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=c;c++)b+='";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 chVolume(a){a=parseInt(domCache.volumeBar.value)+a;0>a?a=0:100e?"0":""):"")+e+"\u2009m "+(10>a?"0":"")+a+"\u2009s"} function genId(a){return"id"+a.replace(/[^\w]/g,"")}appInit(); diff --git a/dist/htdocs/sw.min.js b/dist/htdocs/sw.min.js index 288d5c5..1d069fa 100644 --- a/dist/htdocs/sw.min.js +++ b/dist/htdocs/sw.min.js @@ -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 +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + int max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + int offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = realloc(line, max_line); + if (!new_line) { + free(line); + return -2; + } + line = new_line; + if (reader(line + offset, max_line - offset, stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/dist/src/inih/ini.h b/dist/src/inih/ini.h new file mode 100644 index 0000000..4db7d77 --- /dev/null +++ b/dist/src/inih/ini.h @@ -0,0 +1,130 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/htdocs/index.html b/htdocs/index.html index e0b5088..ea9f31d 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -75,7 +75,7 @@
Playback
-
+

diff --git a/htdocs/js/mpd.js b/htdocs/js/mpd.js index c315dd9..3680ea5 100644 --- a/htdocs/js/mpd.js +++ b/htdocs/js/mpd.js @@ -83,7 +83,8 @@ var modalSavequeue = new Modal(document.getElementById('modalSaveQueue')); var modalSongDetails = new Modal(document.getElementById('modalSongDetails')); var modalAddToPlaylist = new Modal(document.getElementById('modalAddToPlaylist')); var modalRenamePlaylist = new Modal(document.getElementById('modalRenamePlaylist')); -var mainMenu = new Dropdown(document.getElementById('mainMenu')); +//var mainMenu = new Dropdown(document.getElementById('mainMenu')); +//var volumeMenu = new Dropdown(document.getElementById('volumeIcon')); function appPrepare(scrollPos) { if (app.current.app != app.last.app || app.current.tab != app.last.tab || app.current.view != app.last.view) { @@ -287,7 +288,7 @@ function appRoute() { function appInit() { getSettings(); - sendAPI({"cmd":"MPD_API_GET_OUTPUTNAMES"}, parseOutputnames); +// sendAPI({"cmd":"MPD_API_GET_OUTPUTS"}, parseOutputnames); webSocketConnect(); @@ -303,6 +304,10 @@ function appInit() { sendAPI({"cmd": "MPD_API_SET_SEEK", "data": {"songid": current_song.currentSongId, "seek": seekVal}}); } }, false); + + document.getElementById('volumeIcon').parentNode.addEventListener('show.bs.dropdown', function () { + sendAPI({"cmd":"MPD_API_GET_OUTPUTS"}, parseOutputnames); + }); document.getElementById('modalAbout').addEventListener('shown.bs.modal', function () { sendAPI({"cmd": "MPD_API_GET_STATS"}, parseStats); @@ -640,7 +645,6 @@ function webSocketConnect() { showNotification('Connected to myMPD', '', '', 'success'); modalConnectionError.hide(); appRoute(); - sendAPI({"cmd":"MPD_API_GET_OUTPUTNAMES"}, parseOutputnames); } socket.onmessage = function got_packet(msg) { @@ -807,8 +811,10 @@ function parseOutputnames(obj) { var btns = ''; var outputsLen = obj.data.outputs.length; for (var i = 0; i < outputsLen; i++) { - btns += ''; + btns += ''; } domCache.outputs.innerHTML = btns; } @@ -899,10 +905,14 @@ function parseState(obj) { if (last_state == undefined || obj.data.queue_version != last_state.data.queue_version) sendAPI({"cmd": "MPD_API_GET_CURRENT_SONG"}, songChange); - // Set outputs state - var outputsLen = obj.data.outputs.length; - for (var i = 0; i < outputsLen; i++) { - toggleBtn('btnoutput' + obj.data.outputs[i].id, obj.data.outputs[i].state); + + + //clear playback card if not playing + if (obj.data.songpos == '-1') { + domCache.currentTrack.innerText = 'Not playing'; + document.getElementById('currentAlbum').innerText = ''; + document.getElementById('currentArtist').innerText = ''; + document.getElementById('currentCover').style.backgroundImage = ''; } last_state = obj; @@ -1841,7 +1851,7 @@ function songChange(obj) { var htmlNotification = ''; var pageTitle = 'myMPD: '; - document.getElementById('album-cover').style.backgroundImage = 'url("' + obj.data.cover + '")'; + document.getElementById('currentCover').style.backgroundImage = 'url("' + obj.data.cover + '")'; if (typeof obj.data.artist != 'undefined' && obj.data.artist.length > 0 && obj.data.artist != '-') { textNotification += obj.data.artist; diff --git a/htdocs/sw.js b/htdocs/sw.js index 365acce..df311ee 100644 --- a/htdocs/sw.js +++ b/htdocs/sw.js @@ -1,4 +1,4 @@ -var CACHE = 'myMPD-cache-v3.4.0'; +var CACHE = 'myMPD-cache-v3.5.0'; var urlsToCache = [ '/', '/player.html', diff --git a/mkrelease.sh b/mkrelease.sh index 84dc66a..c9f0b31 100755 --- a/mkrelease.sh +++ b/mkrelease.sh @@ -81,4 +81,4 @@ else fi echo "myMPD installed" -echo "Edit /etc/mympd/options before starting mympd" +echo "Edit /etc/mympd/mympd.conf before starting mympd" diff --git a/src/mpd_client.c b/src/mpd_client.c index 5242df7..2401bb8 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -43,17 +43,15 @@ const char * mpd_cmd_strs[] = { MPD_CMDS(GEN_STR) }; -static inline enum mpd_cmd_ids get_cmd_id(const char *cmd) -{ - for(int i = 0; i < sizeof(mpd_cmd_strs)/sizeof(mpd_cmd_strs[0]); i++) +static inline enum mpd_cmd_ids get_cmd_id(const char *cmd) { + for (int i = 0; i < sizeof(mpd_cmd_strs) / sizeof(mpd_cmd_strs[0]); i++) if (!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i]))) return i; return -1; } -void callback_mympd(struct mg_connection *nc, const struct mg_str msg) -{ +void callback_mympd(struct mg_connection *nc, const struct mg_str msg) { size_t n = 0; char *cmd; unsigned int uint_buf1, uint_buf2, uint_rc; @@ -81,41 +79,41 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown request\"}"); break; case MPD_API_SET_SETTINGS: - json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b); + 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); - json_fprintf(tmpfile, "{ notificationWeb: %d, notificationPage: %d}", state.a, state.b); - rename(tmpfile,mpd.statefile); + snprintf(tmpfile,200,"%s.tmp", config.statefile); + json_fprintf(tmpfile, "{notificationWeb: %d, notificationPage: %d}", state.a, state.b); + rename(tmpfile,config.statefile); - je = json_scanf(msg.p, msg.len, "{ data: { random:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {random:%u}}", &uint_buf1); if (je == 1) mpd_run_random(mpd.conn, uint_buf1); - je = json_scanf(msg.p, msg.len, "{ data: { repeat:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {repeat:%u}}", &uint_buf1); if (je == 1) mpd_run_repeat(mpd.conn, uint_buf1); - je = json_scanf(msg.p, msg.len, "{ data: { consume:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {consume:%u}}", &uint_buf1); if (je == 1) mpd_run_consume(mpd.conn, uint_buf1); - je = json_scanf(msg.p, msg.len, "{ data: { single:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {single:%u}}", &uint_buf1); if (je == 1) mpd_run_single(mpd.conn, uint_buf1); - je = json_scanf(msg.p, msg.len, "{ data: { crossfade:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {crossfade:%u}}", &uint_buf1); if (je == 1) mpd_run_crossfade(mpd.conn, uint_buf1); - je = json_scanf(msg.p, msg.len, "{ data: { mixrampdb:%f } }", &float_buf); + je = json_scanf(msg.p, msg.len, "{data: {mixrampdb:%f}}", &float_buf); if (je == 1) mpd_run_mixrampdb(mpd.conn, float_buf); - je = json_scanf(msg.p, msg.len, "{ data: { mixrampdelay:%f } }", &float_buf); + je = json_scanf(msg.p, msg.len, "{data: {mixrampdelay:%f}}", &float_buf); if (je == 1) mpd_run_mixrampdelay(mpd.conn, float_buf); - je = json_scanf(msg.p, msg.len, "{ data: { replaygain:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {replaygain:%Q}}", &p_charbuf1); if (je == 1) { mpd_send_command(mpd.conn, "replay_gain_mode", p_charbuf1, NULL); struct mpd_pair *pair; @@ -127,7 +125,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); break; case MPD_API_GET_ARTISTALBUMTITLES: - je = json_scanf(msg.p, msg.len, "{ data: { albumartist:%Q, album:%Q } }", &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {albumartist:%Q, album:%Q}}", &p_charbuf1, &p_charbuf2); if (je == 2) { n = mympd_put_songs_in_album(mpd.buf, p_charbuf1, p_charbuf2); free(p_charbuf1); @@ -170,21 +168,21 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) n = mympd_queue_crop(mpd.buf); break; case MPD_API_RM_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { track:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {track:%u}}", &uint_buf1); if (je == 1) { mpd_run_delete_id(mpd.conn, uint_buf1); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); } break; case MPD_API_RM_RANGE: - je = json_scanf(msg.p, msg.len, "{ data: { start:%u, end:%u } }", &uint_buf1, &uint_buf2); + je = json_scanf(msg.p, msg.len, "{data: {start:%u, end:%u}}", &uint_buf1, &uint_buf2); if (je == 2) { mpd_run_delete_range(mpd.conn, uint_buf1, uint_buf2); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); } break; case MPD_API_MOVE_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { from:%u, to:%u } }", &uint_buf1, &uint_buf2); + je = json_scanf(msg.p, msg.len, "{data: {from:%u, to:%u}}", &uint_buf1, &uint_buf2); if (je == 2) { uint_buf1 --; uint_buf2 --; @@ -195,7 +193,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_PLAYLIST_MOVE_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { plist: %Q, from:%u, to:%u } }", &p_charbuf1, &uint_buf1, &uint_buf2); + je = json_scanf(msg.p, msg.len, "{data: {plist: %Q, from:%u, to:%u }}", &p_charbuf1, &uint_buf1, &uint_buf2); if (je == 3) { uint_buf1 --; uint_buf2 --; @@ -208,17 +206,17 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_PLAY_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { track:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: { track:%u}}", &uint_buf1); if (je == 1) { mpd_run_play_id(mpd.conn, uint_buf1); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); } break; - case MPD_API_GET_OUTPUTNAMES: - n = mympd_put_outputnames(mpd.buf); + case MPD_API_GET_OUTPUTS: + n = mympd_put_outputs(mpd.buf); break; case MPD_API_TOGGLE_OUTPUT: - je = json_scanf(msg.p, msg.len, "{ data: { output:%u, state:%u } }", &uint_buf1, &uint_buf2); + je = json_scanf(msg.p, msg.len, "{data: {output:%u, state:%u}}", &uint_buf1, &uint_buf2); if (je == 2) { if (uint_buf2) mpd_run_enable_output(mpd.conn, uint_buf1); @@ -228,21 +226,21 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_SET_VOLUME: - je = json_scanf(msg.p, msg.len, "{ data: { volume:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {volume:%u}}", &uint_buf1); if (je == 1) { mpd_run_set_volume(mpd.conn, uint_buf1); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); } break; case MPD_API_SET_SEEK: - je = json_scanf(msg.p, msg.len, "{ data: { songid:%u, seek:%u } }", &uint_buf1, &uint_buf2); + je = json_scanf(msg.p, msg.len, "{data: {songid:%u, seek:%u}}", &uint_buf1, &uint_buf2); if (je == 2) { mpd_run_seek_id(mpd.conn, uint_buf1, uint_buf2); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); } break; case MPD_API_GET_QUEUE: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u } }", &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u }}", &uint_buf1); if (je == 1) { n = mympd_put_queue(mpd.buf, uint_buf1); } @@ -251,21 +249,21 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) n = mympd_put_current_song(mpd.buf); break; case MPD_API_GET_SONGDETAILS: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: { uri:%Q }}", &p_charbuf1); if (je == 1) { n = mympd_put_songdetails(mpd.buf, p_charbuf1); free(p_charbuf1); } break; case MPD_API_GET_ARTISTS: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q } }", &uint_buf1, &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u, filter:%Q}}", &uint_buf1, &p_charbuf1); if (je == 2) { n = mympd_put_db_tag(mpd.buf, uint_buf1, "AlbumArtist", "", "", p_charbuf1); free(p_charbuf1); } break; case MPD_API_GET_ARTISTALBUMS: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q, albumartist:%Q } }", &uint_buf1, &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u, filter:%Q, albumartist:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2); if (je == 3) { n = mympd_put_db_tag(mpd.buf, uint_buf1, "Album", "AlbumArtist", p_charbuf2, p_charbuf1); free(p_charbuf1); @@ -273,7 +271,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_PLAYLIST_RENAME: - je = json_scanf(msg.p, msg.len, "{ data: { from:%Q, to:%Q } }", &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {from:%Q, to:%Q}}", &p_charbuf1, &p_charbuf2); if (je == 2) { mpd_run_rename(mpd.conn, p_charbuf1, p_charbuf2); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Renamed playlist %s to %s\"}", p_charbuf1, p_charbuf2); @@ -282,14 +280,14 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_GET_PLAYLISTS: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q } }", &uint_buf1, &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u, filter:%Q}}", &uint_buf1, &p_charbuf1); if (je == 2) { n = mympd_put_playlists(mpd.buf, uint_buf1, p_charbuf1); free(p_charbuf1); } break; case MPD_API_GET_PLAYLIST_LIST: - je = json_scanf(msg.p, msg.len, "{ data: { uri: %Q, offset:%u, filter:%Q } }", &p_charbuf1, &uint_buf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {uri: %Q, offset:%u, filter:%Q}}", &p_charbuf1, &uint_buf1, &p_charbuf2); if (je == 3) { n = mympd_put_playlist_list(mpd.buf, p_charbuf1, uint_buf1, p_charbuf2); free(p_charbuf1); @@ -297,7 +295,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_ADD_TO_PLAYLIST: - je = json_scanf(msg.p, msg.len, "{ data: { plist:%Q, uri:%Q } }", &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {plist:%Q, uri:%Q}}", &p_charbuf1, &p_charbuf2); if (je == 2) { mpd_run_playlist_add(mpd.conn, p_charbuf1, p_charbuf2); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Added %s to playlist %s\"}", p_charbuf2, p_charbuf1); @@ -306,7 +304,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_PLAYLIST_CLEAR: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1); if (je == 1) { mpd_run_playlist_clear(mpd.conn, p_charbuf1); free(p_charbuf1); @@ -314,7 +312,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_RM_PLAYLIST_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q, track:%u } }", &p_charbuf1, &uint_buf1); + je = json_scanf(msg.p, msg.len, "{data: {uri:%Q, track:%u}}", &p_charbuf1, &uint_buf1); if (je == 2) { mpd_run_playlist_delete(mpd.conn, p_charbuf1, uint_buf1); free(p_charbuf1); @@ -322,7 +320,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_GET_FILESYSTEM: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q, path:%Q } }", &uint_buf1, &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u, filter:%Q, path:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2); if (je == 3) { n = mympd_put_browse(mpd.buf, p_charbuf2, uint_buf1, p_charbuf1); free(p_charbuf1); @@ -330,7 +328,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_ADD_TRACK_AFTER: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q, to:%d } }", &p_charbuf1, &int_buf); + je = json_scanf(msg.p, msg.len, "{data: {uri:%Q, to:%d}}", &p_charbuf1, &int_buf); if (je == 2) { int_rc = mpd_run_add_id_to(mpd.conn, p_charbuf1, int_buf); if (int_rc > -1 ) @@ -339,7 +337,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_REPLACE_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {uri:%Q }}", &p_charbuf1); if (je == 1) { mpd_run_clear(mpd.conn); mpd_run_add(mpd.conn, p_charbuf1); @@ -349,7 +347,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_ADD_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1); if (je == 1) { mpd_run_add(mpd.conn, p_charbuf1); free(p_charbuf1); @@ -357,7 +355,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_ADD_PLAY_TRACK: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {uri:%Q}}", &p_charbuf1); if (je == 1) { int_buf = mpd_run_add_id(mpd.conn, p_charbuf1); if (int_buf != -1) @@ -367,7 +365,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_REPLACE_PLAYLIST: - je = json_scanf(msg.p, msg.len, "{ data: { plist:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {plist:%Q}}", &p_charbuf1); if (je == 1) { mpd_run_clear(mpd.conn); mpd_run_load(mpd.conn, p_charbuf1); @@ -377,7 +375,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_ADD_PLAYLIST: - je = json_scanf(msg.p, msg.len, "{ data: { plist:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: {plist:%Q}}", &p_charbuf1); if (je == 1) { mpd_run_load(mpd.conn, p_charbuf1); free(p_charbuf1); @@ -385,7 +383,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_SAVE_QUEUE: - je = json_scanf(msg.p, msg.len, "{ data: { plist:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{ data: {plist:%Q}}", &p_charbuf1); if (je == 1) { mpd_run_save(mpd.conn, p_charbuf1); free(p_charbuf1); @@ -393,7 +391,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_SEARCH_QUEUE: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, mpdtag:%Q, searchstr:%Q } }", &uint_buf1, &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u, mpdtag:%Q, searchstr:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2); if (je == 3) { n = mympd_search_queue(mpd.buf, p_charbuf1, uint_buf1, p_charbuf2); free(p_charbuf1); @@ -401,7 +399,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_SEARCH_ADD: - je = json_scanf(msg.p, msg.len, "{ data: { filter:%Q, searchstr:%Q } }", &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {filter:%Q, searchstr:%Q}}", &p_charbuf1, &p_charbuf2); if (je == 2) { n = mympd_search_add(mpd.buf, p_charbuf1, p_charbuf2); free(p_charbuf1); @@ -411,7 +409,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_SEARCH_ADD_PLIST: - je = json_scanf(msg.p, msg.len, "{ data: { plist:%Q, filter:%Q, searchstr:%Q } }", &p_charbuf1, &p_charbuf2, &p_charbuf3); + je = json_scanf(msg.p, msg.len, "{data: {plist:%Q, filter:%Q, searchstr:%Q}}", &p_charbuf1, &p_charbuf2, &p_charbuf3); if (je == 3) { n = mympd_search_add_plist(p_charbuf1, p_charbuf2, p_charbuf3); free(p_charbuf1); @@ -422,7 +420,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_SEARCH: - je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, mpdtag:%Q, searchstr:%Q } }", &uint_buf1, &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: {offset:%u, mpdtag:%Q, searchstr:%Q}}", &uint_buf1, &p_charbuf1, &p_charbuf2); if (je == 3) { n = mympd_search(mpd.buf, p_charbuf1, uint_buf1, p_charbuf2); free(p_charbuf1); @@ -434,7 +432,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}"); break; case MPD_API_SEND_MESSAGE: - je = json_scanf(msg.p, msg.len, "{ data: { channel:%Q, text:%Q } }", &p_charbuf1, &p_charbuf2); + je = json_scanf(msg.p, msg.len, "{data: { channel:%Q, text:%Q}}", &p_charbuf1, &p_charbuf2); if (je == 2) { mpd_run_send_message(mpd.conn, p_charbuf1, p_charbuf2); free(p_charbuf1); @@ -443,7 +441,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) } break; case MPD_API_RM_PLAYLIST: - je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1); + je = json_scanf(msg.p, msg.len, "{data: { uri:%Q }}", &p_charbuf1); if (je == 1) { mpd_run_rm(mpd.conn, p_charbuf1); free(p_charbuf1); @@ -454,13 +452,13 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) n = mympd_put_settings(mpd.buf); break; case MPD_API_GET_STATS: - n = mympd_get_stats(mpd.buf); + n = mympd_put_stats(mpd.buf); break; } if (mpd.conn_state == MPD_CONNECTED && mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) { #ifdef DEBUG - fprintf(stderr,"Error: %s\n", mpd_connection_get_error_message(mpd.conn)); + fprintf(stderr, "Error: %s\n", mpd_connection_get_error_message(mpd.conn)); #endif n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}", mpd_connection_get_error_message(mpd.conn)); @@ -475,13 +473,13 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) if (is_websocket(nc)) { #ifdef DEBUG - fprintf(stdout,"Send websocket response:\n %s\n",mpd.buf); + fprintf(stdout, "Send websocket response:\n %s\n",mpd.buf); #endif mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, mpd.buf, n); } else { #ifdef DEBUG - fprintf(stdout,"Send http response:\n %s\n",mpd.buf); + fprintf(stdout, "Send http response:\n %s\n",mpd.buf); #endif mg_send_http_chunk(nc, mpd.buf, n); } @@ -502,9 +500,9 @@ static int mympd_notify_callback(struct mg_connection *c, const char *param) { if (param) { /* error message? */ - n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",param); + n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"%s\"}",param); #ifdef DEBUG - fprintf(stdout,"Error in mpd_notify_callback: %s\n",param); + fprintf(stderr, "Error in mpd_notify_callback: %s\n",param); #endif mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n); return 0; @@ -516,31 +514,31 @@ static int mympd_notify_callback(struct mg_connection *c, const char *param) { struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->user_data; if (mpd.conn_state != MPD_CONNECTED) { - n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}"); + n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"disconnected\"}"); #ifdef DEBUG - fprintf(stdout,"Notify: disconnected\n"); + fprintf(stdout, "Notify: disconnected\n"); #endif mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n); } else { #ifdef DEBUG - fprintf(stdout,"Notify: %s\n",mpd.buf); + fprintf(stdout, "Notify: %s\n",mpd.buf); #endif mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, strlen(mpd.buf)); if (s->song_id != mpd.song_id) { - n=mympd_put_current_song(mpd.buf); + n = mympd_put_current_song(mpd.buf); #ifdef DEBUG - fprintf(stdout,"Notify: %s\n",mpd.buf); + fprintf(stdout, "Notify: %s\n",mpd.buf); #endif mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n); s->song_id = mpd.song_id; } if (s->queue_version != mpd.queue_version) { - n=snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_queue\"}"); + n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"update_queue\"}"); #ifdef DEBUG - fprintf(stdout,"Notify: update_queue\n"); + fprintf(stdout, "Notify: update_queue\n"); #endif mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, mpd.buf, n); s->queue_version = mpd.queue_version; @@ -553,8 +551,8 @@ void mympd_poll(struct mg_mgr *s) { switch (mpd.conn_state) { case MPD_DISCONNECTED: /* Try to connect */ - fprintf(stdout, "MPD Connecting to %s:%d\n", mpd.host, mpd.port); - mpd.conn = mpd_connection_new(mpd.host, mpd.port, 3000); + fprintf(stdout, "MPD Connecting to %s: %d\n", config.mpdhost, config.mpdport); + mpd.conn = mpd_connection_new(config.mpdhost, config.mpdport, 3000); if (mpd.conn == NULL) { fprintf(stderr, "Out of memory."); mpd.conn_state = MPD_FAILURE; @@ -570,7 +568,7 @@ void mympd_poll(struct mg_mgr *s) { return; } - if (mpd.password && !mpd_run_password(mpd.conn, mpd.password)) { + if (config.mpdpass && !mpd_run_password(mpd.conn, config.mpdpass)) { fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn)); for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) { mympd_notify_callback(c, mpd_connection_get_error_message(mpd.conn)); @@ -619,9 +617,7 @@ char* mympd_get_tag(struct mpd_song const *song, enum mpd_tag_type tag) { int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsigned *queue_version) { struct mpd_status *status; const struct mpd_audio_format *audioformat; - struct mpd_output *output; int len; - int nr; struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); status = mpd_run_status(mpd.conn); @@ -656,31 +652,15 @@ int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsi mpd_status_get_queue_version(status) ); - len += json_printf(&out, ",outputs: ["); - - mpd_send_outputs(mpd.conn); - nr=0; - while ((output = mpd_recv_output(mpd.conn)) != NULL) { - if (nr++) len += json_printf(&out, ","); - len += json_printf(&out, "{id: %d, state: %d}", - mpd_output_get_id(output), - mpd_output_get_enabled(output) - ); - mpd_output_free(output); - } - if (!mpd_response_finish(mpd.conn)) { - fprintf(stderr, "MPD outputs: %s\n", mpd_connection_get_error_message(mpd.conn)); - mpd_connection_clear_error(mpd.conn); - } - - len += json_printf(&out, "]}}"); + len += json_printf(&out, "}}"); *current_song_id = mpd_status_get_song_id(status); *next_song_id = mpd_status_get_next_song_id(status); *queue_version = mpd_status_get_queue_version(status); mpd_status_free(status); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr,"Buffer truncated\n"); return len; } @@ -690,7 +670,8 @@ int mympd_put_welcome(char *buffer) { len = json_printf(&out, "{type: welcome, data: { version: %Q}}", MYMPD_VERSION); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr,"Buffer truncated\n"); return len; } @@ -701,8 +682,8 @@ int mympd_put_settings(char *buffer) { int je; struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); struct mympd_state { int a; int b; } state = { .a = 0, .b = 0 }; - if (access( mpd.statefile, F_OK ) != -1 ) { - char *content = json_fread(mpd.statefile); + if (access( config.statefile, F_OK ) != -1 ) { + char *content = json_fread(config.statefile); je = json_scanf(content, strlen(content), "{notificationWeb: %d, notificationPage: %d}", &state.a, &state.b); if (je != 2) { state.a=0; @@ -741,9 +722,11 @@ int mympd_put_settings(char *buffer) { mpd_status_get_random(status), mpd_status_get_mixrampdb(status), mpd_status_get_mixrampdelay(status), - mpd.host, mpd.port, - mpd.password ? "true" : "false", - streamport, coverimage, + config.mpdhost, + config.mpdport, + config.mpdpass ? "true" : "false", + config.streamport, + config.coverimage, MAX_ELEMENTS_PER_PAGE, replaygain, state.a, @@ -751,26 +734,28 @@ int mympd_put_settings(char *buffer) { ); mpd_status_free(status); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } -int mympd_put_outputnames(char *buffer) { +int mympd_put_outputs(char *buffer) { struct mpd_output *output; int len; int nr; struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); - len = json_printf(&out,"{type: outputnames, data: { outputs: ["); + len = json_printf(&out,"{type: outputs, data: {outputs: ["); mpd_send_outputs(mpd.conn); nr=0; while ((output = mpd_recv_output(mpd.conn)) != NULL) { if (nr++) len += json_printf(&out, ","); - len += json_printf(&out,"{id: %d, name: %Q}", + len += json_printf(&out,"{id: %d, name: %Q, state: %d}", mpd_output_get_id(output), - mpd_output_get_name(output) + mpd_output_get_name(output), + mpd_output_get_enabled(output) ); mpd_output_free(output); } @@ -781,23 +766,24 @@ int mympd_put_outputnames(char *buffer) { len += json_printf(&out,"]}}"); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } int mympd_get_cover(const char *uri, char *cover, int cover_len) { - char *path=strdup(uri); + char *path = strdup(uri); int len; - if (strncasecmp("http:",path,5) == 0 ) { - len=snprintf(cover,cover_len,"/assets/coverimage-httpstream.png"); + if (strncasecmp("http:", path, 5) == 0 ) { + len=snprintf(cover,cover_len, "/assets/coverimage-httpstream.png"); } else { dirname(path); - snprintf(cover,cover_len,"%s/library/%s/%s",SRC_PATH,path,coverimage); + snprintf(cover,cover_len,"%s/library/%s/%s", SRC_PATH, path, config.coverimage); if ( access(cover, F_OK ) == -1 ) { - len = snprintf(cover,cover_len,"/assets/coverimage-notavailable.png"); + len = snprintf(cover, cover_len, "/assets/coverimage-notavailable.png"); } else { - len = snprintf(cover,cover_len,"/library/%s/%s",path,coverimage); + len = snprintf(cover, cover_len, "/library/%s/%s", path, config.coverimage); } } return len; @@ -819,7 +805,7 @@ int mympd_put_current_song(char *buffer) { len = json_printf(&out,"{type: song_change, data: { pos: %d, title: %Q, " "artist: %Q, album: %Q, uri: %Q, currentsongid: %d, albumartist: %Q, " - "duration: %d, cover: %Q }}", + "duration: %d, cover: %Q}}", mpd_song_get_pos(song), mympd_get_tag(song, MPD_TAG_TITLE), mympd_get_tag(song, MPD_TAG_ARTIST), @@ -834,7 +820,8 @@ int mympd_put_current_song(char *buffer) { mpd_song_free(song); mpd_response_finish(mpd.conn); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -866,7 +853,8 @@ int mympd_put_songdetails(char *buffer, char *uri) { } len += json_printf(&out, "}}"); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -893,7 +881,7 @@ int mympd_put_queue(char *buffer, unsigned int offset) { entity_count ++; if (entity_count > offset && entity_count <= offset+MAX_ELEMENTS_PER_PAGE) { if (entities_returned ++) len += json_printf(&out,","); - len += json_printf(&out, "{ id: %d, pos: %d, duration: %d, artist: %Q, album: %Q, title: %Q, uri: %Q }", + len += json_printf(&out, "{id: %d, pos: %d, duration: %d, artist: %Q, album: %Q, title: %Q, uri: %Q }", mpd_song_get_id(song), mpd_song_get_pos(song), mpd_song_get_duration(song), @@ -915,7 +903,8 @@ int mympd_put_queue(char *buffer, unsigned int offset) { mpd.queue_version ); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -931,7 +920,7 @@ int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter if (!mpd_send_list_meta(mpd.conn, path)) RETURN_ERROR_AND_RECOVER("mpd_send_list_meta"); - len = json_printf(&out, "{ type: browse, data: [ "); + len = json_printf(&out, "{type: browse, data: [ "); while((entity = mpd_recv_entity(mpd.conn)) != NULL) { const struct mpd_song *song; @@ -997,7 +986,7 @@ int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter ( strncmp(filter,"0",1) == 0 && isalpha(*plName) == 0 ) ) { if (entities_returned ++) len += json_printf(&out,","); - len += json_printf(&out, "{ type: plist, uri: %Q, name: %Q }", + len += json_printf(&out, "{type: plist, uri: %Q, name: %Q }", entityName, plName ); @@ -1023,7 +1012,8 @@ int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter filter ); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1075,7 +1065,8 @@ int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char * filter ); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1087,9 +1078,8 @@ int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album) { char cover[500]; struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE); - if (mpd_search_db_songs(mpd.conn, true) == false) { + if (mpd_search_db_songs(mpd.conn, true) == false) RETURN_ERROR_AND_RECOVER("mpd_search_db_songs"); - } if (mpd_search_add_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, albumartist) == false) RETURN_ERROR_AND_RECOVER("mpd_search_add_tag_constraint"); @@ -1107,7 +1097,7 @@ int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album) { if (entity_count <= MAX_ELEMENTS_PER_PAGE) { if (entities_returned ++) len += json_printf(&out, ", "); else mympd_get_cover(mpd_song_get_uri(song),cover,500); - len += json_printf(&out, "{ type: song, uri: %Q, duration: %d, title: %Q, track: %Q }", + len += json_printf(&out, "{ type: song, uri: %Q, duration: %d, title: %Q, track: %Q}", mpd_song_get_uri(song), mpd_song_get_duration(song), mympd_get_tag(song, MPD_TAG_TITLE), @@ -1117,7 +1107,7 @@ int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album) { mpd_song_free(song); } - len += json_printf(&out, "], totalEntities: %d, returnedEntities: %d, albumartist: %Q, album: %Q, cover: %Q }", + len += json_printf(&out, "], totalEntities: %d, returnedEntities: %d, albumartist: %Q, album: %Q, cover: %Q}", entity_count, entities_returned, albumartist, @@ -1126,7 +1116,8 @@ int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album) { ); } - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1141,7 +1132,7 @@ int mympd_put_playlists(char *buffer, unsigned int offset, char *filter) { if (!mpd_send_list_playlists(mpd.conn)) RETURN_ERROR_AND_RECOVER("mpd_send_lists_playlists"); - len = json_printf(&out, "{ type: playlists, data: [ "); + len = json_printf(&out, "{type: playlists, data: [ "); while((pl = mpd_recv_playlist(mpd.conn)) != NULL) { entity_count ++; @@ -1151,7 +1142,7 @@ int mympd_put_playlists(char *buffer, unsigned int offset, char *filter) { ( strncmp(filter,"0",1) == 0 && isalpha(*plpath) == 0 ) ) { if (entities_returned ++) len += json_printf(&out, ", "); - len += json_printf(&out, "{ type: plist, uri: %Q, name: %Q, last_modified: %d }", + len += json_printf(&out, "{type: plist, uri: %Q, name: %Q, last_modified: %d}", plpath, plpath, mpd_playlist_get_last_modified(pl) @@ -1166,13 +1157,14 @@ int mympd_put_playlists(char *buffer, unsigned int offset, char *filter) { if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) RETURN_ERROR_AND_RECOVER("mpd_send_list_playlists"); - len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d }", + len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d}", entity_count, offset, entities_returned ); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1187,7 +1179,7 @@ int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char * if (!mpd_send_list_playlist_meta(mpd.conn, uri)) RETURN_ERROR_AND_RECOVER("mpd_send_list_meta"); - len = json_printf(&out, "{ type: playlist_detail, data: [ "); + len = json_printf(&out, "{type: playlist_detail, data: [ "); while((entity = mpd_recv_entity(mpd.conn)) != NULL) { const struct mpd_song *song; @@ -1199,7 +1191,7 @@ int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char * ( strncmp(filter,"0",1) == 0 && isalpha(*entityName) == 0 ) ) { if (entities_returned ++) len += json_printf(&out,","); - len += json_printf(&out, "{type: song, uri: %Q, album: %Q, artist: %Q, duration: %d, title: %Q, name: %Q }", + len += json_printf(&out, "{type: song, uri: %Q, album: %Q, artist: %Q, duration: %d, title: %Q, name: %Q}", mpd_song_get_uri(song), mympd_get_tag(song, MPD_TAG_ALBUM), mympd_get_tag(song, MPD_TAG_ARTIST), @@ -1214,7 +1206,7 @@ int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char * mpd_entity_free(entity); } - len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, filter: %Q, uri: %Q }", + len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, filter: %Q, uri: %Q}", entity_count, offset, entities_returned, @@ -1223,7 +1215,7 @@ int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char * ); if (len > MAX_SIZE) - fprintf(stderr,"Buffer truncated\n"); + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1249,13 +1241,13 @@ int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *sear if (mpd_search_commit(mpd.conn) == false) RETURN_ERROR_AND_RECOVER("mpd_search_commit"); else { - len = json_printf(&out, "{ type: search, data: [ "); + len = json_printf(&out, "{type: search, data: [ "); while((song = mpd_recv_song(mpd.conn)) != NULL) { entity_count ++; if (entity_count > offset && entity_count <= offset+MAX_ELEMENTS_PER_PAGE) { if (entities_returned ++) len += json_printf(&out, ", "); - len += json_printf(&out, "{ type: song, uri: %Q, album: %Q, artist: %Q, duration: %d, title: %Q, name: %Q }", + len += json_printf(&out, "{type: song, uri: %Q, album: %Q, artist: %Q, duration: %d, title: %Q, name: %Q}", mpd_song_get_uri(song), mympd_get_tag(song, MPD_TAG_ALBUM), mympd_get_tag(song, MPD_TAG_ARTIST), @@ -1267,7 +1259,7 @@ int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *sear mpd_song_free(song); } - len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, mpdtagtype: %Q }", + len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, mpdtagtype: %Q}", entity_count, offset, entities_returned, @@ -1276,7 +1268,7 @@ int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *sear } if (len > MAX_SIZE) - fprintf(stderr,"Buffer truncated\n"); + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1306,7 +1298,7 @@ int mympd_search_add(char *buffer, char *mpdtagtype, char *searchstr) { } if (len > MAX_SIZE) - fprintf(stderr,"Buffer truncated\n"); + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1321,7 +1313,7 @@ int mympd_search_add_plist(char *plist, char *mpdtagtype, char *searchstr) { } if (len > MAX_SIZE) - fprintf(stderr,"Buffer truncated\n"); + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1335,7 +1327,7 @@ int mympd_queue_crop(char *buffer) { mpd_status_free(status); if (length < 0) { - len = json_printf(&out, "{ type: error, data: %Q }", "A playlist longer than 1 song in length is required to crop."); + len = json_printf(&out, "{type: error, data: %Q }", "A playlist longer than 1 song in length is required to crop."); } else if (mpd_status_get_state(status) == MPD_STATE_PLAY || mpd_status_get_state(status) == MPD_STATE_PAUSE) { playing_song_pos++; @@ -1344,13 +1336,13 @@ int mympd_queue_crop(char *buffer) { playing_song_pos--; if (playing_song_pos > 0 ) mpd_run_delete_range(mpd.conn, 0, playing_song_pos--); - len = json_printf(&out, "{ type: result, data: ok }"); + len = json_printf(&out, "{type: result, data: ok}"); } else { - len = json_printf(&out, "{ type: error, data: %Q }", "You need to be playing to crop the playlist"); + len = json_printf(&out, "{type: error, data: %Q}", "You need to be playing to crop the playlist"); } if (len > MAX_SIZE) - fprintf(stderr,"Buffer truncated\n"); + fprintf(stderr, "Buffer truncated\n"); return len; } @@ -1383,7 +1375,7 @@ int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char entity_count ++; if (entity_count > offset && entity_count <= offset+MAX_ELEMENTS_PER_PAGE) { if (entities_returned ++) len += json_printf(&out, ", "); - len += json_printf(&out, "{ type: song, id: %d, pos: %d, album: %Q, artist: %Q, duration: %d, title: %Q }", + len += json_printf(&out, "{type: song, id: %d, pos: %d, album: %Q, artist: %Q, duration: %d, title: %Q}", mpd_song_get_id(song), mpd_song_get_pos(song), mympd_get_tag(song, MPD_TAG_ALBUM), @@ -1395,7 +1387,7 @@ int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char } } - len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, mpdtagtype: %Q }", + len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, mpdtagtype: %Q}", entity_count, offset, entities_returned, @@ -1403,11 +1395,12 @@ int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char ); } - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr, "Buffer truncated\n"); return len; } -int mympd_get_stats(char *buffer) { +int mympd_put_stats(char *buffer) { struct mpd_stats *stats = mpd_run_stats(mpd.conn); const unsigned *version = mpd_connection_get_server_version(mpd.conn); char mpd_version[20]; @@ -1417,9 +1410,9 @@ int mympd_get_stats(char *buffer) { snprintf(mpd_version,20,"%i.%i.%i", version[0], version[1], version[2]); if (stats == NULL) - RETURN_ERROR_AND_RECOVER("mympd_get_stats"); - len = json_printf(&out, "{ type: mpdstats, data: { artists: %d, albums: %d, songs: %d, " - "playtime: %d, uptime: %d, dbupdated: %d, dbplaytime: %d, mympd_version: %Q, mpd_version: %Q }}", + RETURN_ERROR_AND_RECOVER("mympd_put_stats"); + len = json_printf(&out, "{type: mpdstats, data: { artists: %d, albums: %d, songs: %d, " + "playtime: %d, uptime: %d, dbupdated: %d, dbplaytime: %d, mympd_version: %Q, mpd_version: %Q}}", mpd_stats_get_number_of_artists(stats), mpd_stats_get_number_of_albums(stats), mpd_stats_get_number_of_songs(stats), @@ -1432,7 +1425,8 @@ int mympd_get_stats(char *buffer) { ); mpd_stats_free(stats); - if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); + if (len > MAX_SIZE) + fprintf(stderr,"Buffer truncated\n"); return len; } diff --git a/src/mpd_client.h b/src/mpd_client.h index 1d09893..6c3cde8 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -35,7 +35,7 @@ if (!mpd_connection_clear_error(mpd.conn)) \ mpd.conn_state = MPD_FAILURE; \ return len; \ -} while(0) +} while (0) #define MAX_SIZE 1024 * 100 @@ -77,7 +77,7 @@ X(MPD_API_SET_NEXT) \ X(MPD_API_SET_PREV) \ X(MPD_API_UPDATE_DB) \ - X(MPD_API_GET_OUTPUTNAMES) \ + X(MPD_API_GET_OUTPUTS) \ X(MPD_API_TOGGLE_OUTPUT) \ X(MPD_API_SEND_SHUFFLE) \ X(MPD_API_GET_STATS) \ @@ -107,11 +107,6 @@ enum mpd_conn_states { }; struct t_mpd { - int port; - char host[128]; - char *password; - char *statefile; - struct mpd_connection *conn; enum mpd_conn_states conn_state; @@ -124,8 +119,22 @@ struct t_mpd { unsigned queue_version; } mpd; -int streamport; -char coverimage[40]; +typedef struct { + int mpdport; + const char* mpdhost; + const char* mpdpass; + const char* webport; + bool ssl; + const char* sslport; + const char* sslcert; + const char* sslkey; + const char* user; + int streamport; + const char* coverimage; + const char* statefile; +} configuration; + +configuration config; static int is_websocket(const struct mg_connection *nc) { return nc->flags & MG_F_IS_WEBSOCKET; @@ -141,7 +150,7 @@ void mympd_poll(struct mg_mgr *s); void callback_mympd(struct mg_connection *nc, const struct mg_str msg); int mympd_close_handler(struct mg_connection *c); int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsigned *queue_version); -int mympd_put_outputnames(char *buffer); +int mympd_put_outputs(char *buffer); int mympd_put_current_song(char *buffer); int mympd_put_queue(char *buffer, unsigned int offset); int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter); @@ -150,7 +159,7 @@ int mympd_search_add(char *buffer, char *mpdtagtype, char *searchstr); int mympd_search_add_plist(char *plist, char *mpdtagtype, char *searchstr); int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr); int mympd_put_welcome(char *buffer); -int mympd_get_stats(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 *albumartist, char *album); diff --git a/src/mympd.c b/src/mympd.c index bd96b7e..6474077 100644 --- a/src/mympd.c +++ b/src/mympd.c @@ -26,35 +26,34 @@ #include #include #include -#include #include #include #include "../dist/src/mongoose/mongoose.h" +#include "../dist/src/inih/ini.h" #include "mpd_client.h" #include "config.h" -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 - s_signal_received = 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)) { + 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 */ - } } static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { @@ -63,14 +62,14 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { #ifdef DEBUG fprintf(stdout,"New Websocket connection\n"); #endif - struct mg_str d = {(char *) "{\"cmd\":\"MPD_API_WELCOME\"}", 25 }; + struct mg_str d = {(char *) "{\"cmd\": \"MPD_API_WELCOME\"}", 25 }; callback_mympd(nc, d); break; } case MG_EV_HTTP_REQUEST: { struct http_message *hm = (struct http_message *) ev_data; #ifdef DEBUG - printf("HTTP request: %.*s\n",hm->uri.len,hm->uri.p); + printf("HTTP request: %.*s\n", hm->uri.len, hm->uri.p); #endif if (mg_vcmp(&hm->uri, "/api") == 0) { handle_api(nc, hm); @@ -107,114 +106,85 @@ static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data } } +static int inihandler(void* user, const char* section, const char* name, const char* value) { + configuration* p_config = (configuration*)user; + + #define MATCH(n) strcmp(name, n) == 0 + if (MATCH("mpdhost")) + p_config->mpdhost = strdup(value); + else if (MATCH("mpdhost")) + p_config->mpdhost = strdup(value); + else if (MATCH("mpdport")) + p_config->mpdport = atoi(value); + else if (MATCH("mpdhost")) + p_config->mpdhost = strdup(value); + 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("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 = atoi(value); + else if (MATCH("coverimage")) + p_config->coverimage = strdup(value); + else if (MATCH("statefile")) + p_config->statefile = strdup(value); + else + return 0; /* unknown section/name, error */ + + return 1; +} + 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/ssl/server.pem"; - char *s_ssl_key = "/etc/mympd/ssl/server.key"; + char hostname[1024]; hostname[1023] = '\0'; - gethostname(hostname, 1023); + gethostname(hostname, 1023); - static struct option long_options[] = { - {"mpdhost", required_argument, 0, 'h'}, - {"mpdport", required_argument, 0, 'p'}, - {"mpdpass", required_argument, 0, 'm'}, - {"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'}, - {"streamport", required_argument, 0, 's'}, - {"coverimage", required_argument, 0, 'i'}, - {"statefile", required_argument, 0, 't'}, - {"version", no_argument, 0, 'v'}, - {"help", no_argument, 0, 0 }, - {0, 0, 0, 0 } - }; - - 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': - mpd.statefile = strdup(optarg); - break; - case 'h': - strncpy(mpd.host, optarg, sizeof(mpd.host)); - break; - case 'p': - mpd.port = atoi(optarg); - break; - 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; - case 'm': - if (strlen(optarg) > 0) - mpd.password = strdup(optarg); - break; - case 's': - streamport = atoi(optarg); - break; - case 'i': - strncpy(coverimage, optarg, sizeof(coverimage)); - break; - case 'v': - fprintf(stdout, "myMPD %d.%d.%d\n" - "Copyright (C) 2018 Juergen Mang \n" - "Built " __DATE__ " "__TIME__"\n", - MYMPD_VERSION_MAJOR, MYMPD_VERSION_MINOR, MYMPD_VERSION_PATCH); - return EXIT_SUCCESS; - break; - default: - 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 [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/ssl/server.pem]\n" - " -K, --sslkey \tfilename for ssl key [/etc/mympd/ssl/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" - " -s, --streamport \tconnect to mpd http stream at port [8000]\n" - " -i, --coverimage \tfilename for coverimage [folder.jpg]\n" - " -t, --statefile \tfilename for mympd state [/var/lib/mympd/mympd.state]\n" - " --help\t\t\t\tthis help\n" - , argv[0]); - return EXIT_FAILURE; + //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 = "nobody"; + config.streamport = 8000; + config.coverimage = "folder.jpg"; + config.statefile = "/var/lib/mympd/mympd.state"; + + if (argc == 2) { + if (ini_parse(argv[1], inihandler, &config) < 0) { + printf("Can't load '%s'\n", argv[1]); + return EXIT_FAILURE; } - + } + else { + fprintf(stdout, "myMPD %s\n" + "Copyright (C) 2018 Juergen Mang \n" + "https://github.com/jcorporation/myMPD\n" + "Built " __DATE__ " "__TIME__"\n\n", + MYMPD_VERSION); + printf("Usage: %s /path/to/mympd.conf\n", argv[0]); + return EXIT_FAILURE; } signal(SIGTERM, signal_handler); @@ -224,42 +194,45 @@ int main(int argc, char **argv) { mg_mgr_init(&mgr, NULL); - if (ssl == true) { - snprintf(s_redirect, 200, "https://%s:%s/", hostname, sslport); - nc_http = mg_bind(&mgr, webport, ev_handler_http); + if (config.ssl == true) { + snprintf(s_redirect, 200, "https://%s:%s/", hostname, config.sslport); + nc_http = mg_bind(&mgr, config.webport, ev_handler_http); if (nc_http == NULL) { - fprintf(stderr, "Error starting server on port %s\n", webport ); + fprintf(stderr, "Error starting server on port %s\n", config.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.ssl_cert = config.sslcert; + bind_opts.ssl_key = config.sslkey; bind_opts.error_string = &err; - nc = mg_bind_opt(&mgr, sslport, ev_handler, bind_opts); + nc = mg_bind_opt(&mgr, config.sslport, ev_handler, bind_opts); if (nc == NULL) { - fprintf(stderr, "Error starting server on port %s: %s\n", sslport, err); + fprintf(stderr, "Error starting server on port %s: %s\n", config.sslport, err); return EXIT_FAILURE; } } else { - nc = mg_bind(&mgr, webport, ev_handler); + nc = mg_bind(&mgr, config.webport, ev_handler); if (nc == NULL) { - fprintf(stderr, "Error starting server on port %s\n", webport ); + fprintf(stderr, "Error starting server on port %s\n", config.webport ); return EXIT_FAILURE; } } - if (run_as_user != NULL) { + if (config.user != NULL) { printf("Droping privileges\n"); struct passwd *pw; - if ((pw = getpwnam(run_as_user)) == NULL) { + if ((pw = getpwnam(config.user)) == NULL) { printf("Unknown user\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; } } @@ -270,16 +243,16 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - if (ssl == true) + 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"; - printf("myMPD started on http port %s\n", webport); - if (ssl == true) - printf("myMPD started on ssl port %s\n", sslport); + printf("myMPD started on http port %s\n", config.webport); + if (config.ssl == true) + printf("myMPD started on ssl port %s\n", config.sslport); while (s_signal_received == 0) { mg_mgr_poll(&mgr, 200);