Explorar o código

better json generator, various fixups

Andrew Karpow %!s(int64=10) %!d(string=hai) anos
pai
achega
b9a4d83130
Modificáronse 9 ficheiros con 337 adicións e 128 borrados
  1. 10 3
      .drone.yml
  2. 2 1
      CMakeLists.txt
  3. 26 2
      htdocs/index.html
  4. 106 46
      htdocs/js/mpd.js
  5. 58 0
      src/json_encode.c
  6. 27 0
      src/json_encode.h
  7. 94 65
      src/mpd_client.c
  8. 4 2
      src/mpd_client.h
  9. 10 9
      src/ympd.c

+ 10 - 3
.drone.yml

@@ -1,10 +1,17 @@
 image: gcc4.8
 script:
   - sudo apt-get install -y libmpdclient-dev &> /dev/null
-  - cmake . -DCMAKE_INSTALL_PREFIX:PATH=/usr
-  - make
-  - cpack -D CPACK_DEBIAN_PACKAGE_MAINTAINER="ympd Team" -G DEB
+  - cmake . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=Debug
+  - make VERBOSE=1 
 notify:
   email:
     recipients:
       - andy@ndyk.de
+  irc:
+    server: irc.freenode.org
+    nick: droneBot
+    channel: '#nicotest'
+    on_started: true
+    on_success: true
+    on_failure: true
+

+ 2 - 1
CMakeLists.txt

@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.6)
 project (ympd)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
 set(CPACK_PACKAGE_VERSION_MAJOR "1")
-set(CPACK_PACKAGE_VERSION_MINOR "0")
+set(CPACK_PACKAGE_VERSION_MINOR "2")
 set(CPACK_PACKAGE_VERSION_PATCH "0")
 
 option(WITH_MPD_HOST_CHANGE "Let users of the web frontend change the MPD Host" ON)
@@ -39,6 +39,7 @@ set(SOURCES
     src/http_server.c
     src/mpd_client.c
     src/mongoose.c
+    src/json_encode.c
 )
 
 add_executable(ympd ${SOURCES} ${PROJECT_BINARY_DIR}/http_files.h)

+ 26 - 2
htdocs/index.html

@@ -39,7 +39,7 @@
 
         <ul id="nav_links" class="nav navbar-nav">
           <li id="queue"><a href="#/">Queue</a></li>
-          <li id="browse"><a href="#/browse/">Browse database</a></li>
+          <li id="browse"><a href="#/browse/0/">Browse database</a></li>
           <li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
         </ul>
 
@@ -82,7 +82,7 @@
         
         <div class="panel panel-primary">
           <!-- Default panel contents -->
-          <div id="panel-heading" class="panel-heading">Queue</div>
+          <div class="panel-heading"><b id="panel-heading">Queue</b></div>
           <div class="panel-body">
             <h1>
               <span id="track-icon" class="glyphicon glyphicon-play"></span>
@@ -116,6 +116,10 @@
             </tbody>
           </table>
         </div><!-- /.panel -->
+        <ul class="pager">
+          <li id="prev" class="page-btn hide"><a href="">Previous</a></li>
+          <li id="next" class="page-btn"><a href="">Next</a></li>
+        </ul>
       </div><!-- /.col-md-10 -->
 
       <div class="col-md-2 col-xs-12" >
@@ -192,6 +196,10 @@
                 data-placement="right" data-toggle="popover" data-content="Password does not match!"
                 data-trigger="manual" />
               </div>
+              <div class="form-group col-md-12">
+                <div id="mpd_password_set" class="hide alert alert-info">MPD Password is set</div>
+              </div>
+
             </div>
           </form>
         </div>
@@ -203,6 +211,22 @@
     </div><!-- /.modal-dialog -->
   </div><!-- /.modal -->
 
+  <div class="modal fade bs-example-modal-sm" id="wait" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h1>Searching...</h1>
+        </div>
+        <div class="modal-body">
+          <div class="progress progress-striped active">
+            <div class="progress-bar"  role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
+              <span class="sr-only">Please Wait</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 
 
   <!-- Bootstrap core JavaScript

+ 106 - 46
htdocs/js/mpd.js

@@ -19,34 +19,49 @@
 var socket;
 var last_state;
 var current_app;
+var pagination = 0;
+var browsepath;
 var lastSongTitle = "";
 var current_song = new Object();
+var MAX_ELEMENTS_PER_PAGE = 512;
 
 var app = $.sammy(function() {
-    this.before('/', function(e, data) {
-        $('#nav_links > li').removeClass('active');
-    });
 
-    this.get('#/', function() {
+    function runBrowse() {
         current_app = 'queue';
+
         $('#breadcrump').addClass('hide');
         $('#salamisandwich').find("tr:gt(0)").remove();
-        socket.send('MPD_API_GET_QUEUE');
+        socket.send('MPD_API_GET_QUEUE,'+pagination);
 
         $('#panel-heading').text("Queue");
         $('#queue').addClass('active');
+    }
+
+    function prepare() {
+        $('#nav_links > li').removeClass('active');
+        $('.page-btn').addClass('hide');
+        pagination = 0;
+        browsepath = '';
+    }
+
+    this.get(/\#\/(\d+)/, function() {
+        prepare();
+        pagination = parseInt(this.params['splat'][0]);
+        runBrowse();
     });
 
-    this.get(/\#\/browse\/(.*)/, function() {
+    this.get(/\#\/browse\/(\d+)\/(.*)/, function() {
+        prepare();
+        browsepath = this.params['splat'][1];
+        pagination = parseInt(this.params['splat'][0]);
         current_app = 'browse';
-        $('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/\">root</a></li>");
+        $('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/0/\">root</a></li>");
         $('#salamisandwich').find("tr:gt(0)").remove();
-        var path = this.params['splat'][0];
-
-        socket.send('MPD_API_GET_BROWSE,' + path);
+        socket.send('MPD_API_GET_BROWSE,'+pagination+','+(browsepath ? browsepath : "/"));
 
-        $('#panel-heading').text("Browse database: "+path);
-        var path_array = path.split('/');
+        $('#panel-heading').text("Browse database: "+browsepath);
+        var path_array = browsepath.split('/');
         var full_path = "";
         $.each(path_array, function(index, chunk) {
             if(path_array.length - 1 == index) {
@@ -55,7 +70,7 @@ var app = $.sammy(function() {
             }
 
             full_path = full_path + chunk;
-            $('#breadcrump').append("<li><a href=\"#/browse/" + full_path + "\">"+chunk+"</a></li>");
+            $('#breadcrump').append("<li><a href=\"#/browse/0/" + full_path + "\">"+chunk+"</a></li>");
             full_path += "/";
         });
         $('#browse').addClass('active');
@@ -73,7 +88,7 @@ var app = $.sammy(function() {
     });
 
     this.get("/", function(context) {
-        context.redirect("#/");
+        context.redirect("#/0");
     });
 });
 
@@ -117,8 +132,8 @@ function webSocketConnect() {
             app.run();
         }
 
-        socket.onmessage =function got_packet(msg) {
-            if(msg.data === last_state)
+        socket.onmessage = function got_packet(msg) {
+            if(msg.data === last_state || msg.data.length == 0)
                 return;
 
             var obj = JSON.parse(msg.data);
@@ -140,6 +155,11 @@ function webSocketConnect() {
                         "</td><td></td></tr>");
                     }
 
+                    if(obj.data[obj.data.length-1].id >= pagination + MAX_ELEMENTS_PER_PAGE)
+                        $('#next').removeClass('hide');
+                    if(pagination > 0)
+                        $('#prev').removeClass('hide');
+
                     $('#salamisandwich > tbody > tr').on({
                         mouseover: function(){
                             if($(this).children().last().has("a").length == 0)
@@ -160,6 +180,7 @@ function webSocketConnect() {
                     });
                     break;
                 case "search":
+                    $('#wait').modal('hide');
                 case "browse":
                     if(current_app !== 'browse' && current_app !== 'search')
                         break;
@@ -194,7 +215,22 @@ function webSocketConnect() {
                                     "</td><td></td></tr>"
                                 );
                                 break;
+                            case "wrap":
+                                if(current_app == 'browse') {
+                                    $('#next').removeClass('hide');
+                                } else {
+                                    $('#salamisandwich > tbody').append(
+                                        "<tr><td><span class=\"glyphicon glyphicon-remove\"></span></td>" + 
+                                        "<td>Too many results, please refine your search!</td>" + 
+                                        "<td></td><td></td></tr>"
+                                    );
+                                }
+                                break;
                         }
+
+                        if(pagination > 0)
+                            $('#prev').removeClass('hide');
+
                     }
 
                     function appendClickableIcon(appendTo, onClickAction, glyphicon) {
@@ -219,21 +255,27 @@ function webSocketConnect() {
                                 appendClickableIcon($(this), 'MPD_API_ADD_PLAY_TRACK', 'play');
                         },
                         click: function() {
-                            if($(this).is(".dir"))
-                                app.setLocation("#/browse/"+$(this).attr("uri"));
-                            else {
-                                if($(this).is(".song"))
+                            switch($(this).attr('class')) {
+                                case 'dir':
+                                    app.setLocation("#/browse/0/"+$(this).attr("uri"));
+                                    break;
+                                case 'song':
                                     socket.send("MPD_API_ADD_TRACK," + $(this).attr("uri"));
-                                else 
+                                    $('.top-right').notify({
+                                        message:{
+                                            text: $('td:nth-child(2)', this).text() + " added"
+                                        }
+                                    }).show();
+                                    break;
+                                case 'plist':
                                     socket.send("MPD_API_ADD_PLAYLIST," + $(this).attr("uri"));
-
-                                $('.top-right').notify({
-                                    message:{
-                                        text: $('td:nth-child(2)', this).text() + " added"
-                                    }
-                                }).show();
+                                    $('.top-right').notify({
+                                        message:{
+                                            text: "Playlist " + $('td:nth-child(2)', this).text() + " added"
+                                        }
+                                    }).show();
+                                    break;
                             }
-
                         },
                         mouseleave: function(){
                             $(this).children().last().find("a").stop().remove();
@@ -300,7 +342,7 @@ function webSocketConnect() {
                     break;
                 case "update_queue":
                     if(current_app === 'queue')
-                        socket.send('MPD_API_GET_QUEUE');
+                        socket.send('MPD_API_GET_QUEUE,'+pagination);
                     break;
                 case "song_change":
                     $('#currenttrack').text(" " + obj.data.title);
@@ -327,10 +369,8 @@ function webSocketConnect() {
                 case "mpdhost":
                     $('#mpdhost').val(obj.data.host);
                     $('#mpdport').val(obj.data.port);
-                    if(obj.data.passwort_set) {
-                        $('#mpd_pw').attr('placeholder', '*******');
-                        $('#mpd_pw_con').attr('placeholder', '*******');
-                    }
+                    if(obj.data.passwort_set)
+                        $('#mpd_password_set').removeClass('hide');
                     break;
                 case "error":
                     $('.top-right').notify({
@@ -481,11 +521,38 @@ function getHost() {
 }
 
 $('#search').submit(function () {
-
     app.setLocation("#/search/"+$('#search > div > input').val());
+    $('#wait').modal('show');
+    setTimeout(function() {
+        $('#wait').modal('hide');
+    }, 10000);
     return false;
 });
 
+$('.page-btn').on('click', function (e) {
+
+    switch ($(this).text()) {
+        case "Next":
+            pagination += MAX_ELEMENTS_PER_PAGE;
+            break;
+        case "Previous":
+            pagination -= MAX_ELEMENTS_PER_PAGE;
+            if(pagination <= 0)
+                pagination = 0;
+            break;
+    }
+
+    switch(current_app) {
+        case "queue":
+            app.setLocation('#/'+pagination);
+            break;
+        case "browse":
+            app.setLocation('#/browse/'+pagination+'/'+browsepath);
+            break;
+    }
+    e.preventDefault();
+});
+
 function confirmSettings() {
     if($('#mpd_pw').val().length + $('#mpd_pw_con').val().length > 0) {
         if ($('#mpd_pw').val() !== $('#mpd_pw_con').val())
@@ -507,16 +574,9 @@ function notificationsSupported() {
 }
 
 function songNotify(artist, title) {
-    if (!notificationsSupported())
-	   return;
-
-    if (window.webkitNotifications.checkPermission() == 0) {
-	   var notification = window.webkitNotifications.createNotification("assets/favicon.ico", artist, title);
-	   notification.show();
-	   setTimeout(function(notification) {
-		  notification.cancel();
-	   }, 3000, notification);
-    } else {
-	   window.webkitNotifications.requestPermission();
-    }
+	var notification = window.webkitNotifications.createNotification("assets/favicon.ico", artist, title);
+	notification.show();
+	setTimeout(function(notification) {
+	   notification.cancel();
+	}, 3000, notification);
 }

+ 58 - 0
src/json_encode.c

@@ -0,0 +1,58 @@
+// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
+// Copyright (c) 2013 Cesanta Software Limited
+// All rights reserved
+//
+// This library is dual-licensed: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation. For the terms of this
+// license, see <http://www.gnu.org/licenses/>.
+//
+// You are free to use this library under the terms of the GNU General
+// Public License, but WITHOUT ANY WARRANTY; without even the implied
+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// Alternatively, you can license this library under a commercial
+// license, as set out in <http://cesanta.com/products.html>.
+
+// json encoder 'frozen' from https://github.com/cesanta/frozen
+
+#include <stdio.h>
+
+#include "json_encode.h"
+
+int json_emit_int(char *buf, int buf_len, long int value) {
+  return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%ld", value);
+}
+
+int json_emit_double(char *buf, int buf_len, double value) {
+  return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%g", value);
+}
+
+int json_emit_quoted_str(char *buf, int buf_len, const char *str) {
+  int i = 0, j = 0, ch;
+
+#define EMIT(x) do { if (j < buf_len) buf[j++] = x; } while (0)
+
+  EMIT('"');
+  while ((ch = str[i++]) != '\0' && j < buf_len) {
+    switch (ch) {
+      case '"':  EMIT('\\'); EMIT('"'); break;
+      case '\\': EMIT('\\'); EMIT('\\'); break;
+      case '\b': EMIT('\\'); EMIT('b'); break;
+      case '\f': EMIT('\\'); EMIT('f'); break;
+      case '\n': EMIT('\\'); EMIT('n'); break;
+      case '\r': EMIT('\\'); EMIT('r'); break;
+      case '\t': EMIT('\\'); EMIT('t'); break;
+      default: EMIT(ch);
+    }
+  }
+  EMIT('"');
+  EMIT(0);
+
+  return j == 0 ? 0 : j - 1;
+}
+
+int json_emit_raw_str(char *buf, int buf_len, const char *str) {
+  return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%s", str);
+}

+ 27 - 0
src/json_encode.h

@@ -0,0 +1,27 @@
+/* ympd
+   (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
+   This project's homepage is: http://www.ympd.org
+   
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation, Inc.,
+   Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+   
+#ifndef __JSON_ENCODE_H__
+#define __JSON_ENCODE_H__
+
+int json_emit_int(char *buf, int buf_len, long int value);
+int json_emit_double(char *buf, int buf_len, double value);
+int json_emit_quoted_str(char *buf, int buf_len, const char *str);
+int json_emit_raw_str(char *buf, int buf_len, const char *str);
+
+#endif

+ 94 - 65
src/mpd_client.c

@@ -25,6 +25,7 @@
 
 #include "mpd_client.h"
 #include "config.h"
+#include "json_encode.h"
 
 const char * mpd_cmd_strs[] = {
     MPD_CMDS(GEN_STR)
@@ -77,9 +78,6 @@ int callback_mpd(struct mg_connection *c)
         case MPD_API_RM_ALL:
             mpd_run_clear(mpd.conn);
             break;
-        case MPD_API_GET_QUEUE:
-            n = mpd_put_queue(mpd.buf);
-            break;
         case MPD_API_RM_TRACK:
             if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
                 mpd_run_delete_id(mpd.conn, uint_buf);
@@ -112,12 +110,16 @@ int callback_mpd(struct mg_connection *c)
             if(sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2))
                 mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2);
             break;
+        case MPD_API_GET_QUEUE:
+            if(sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
+                n = mpd_put_queue(mpd.buf, uint_buf);
+            break;
         case MPD_API_GET_BROWSE:
-            if(sscanf(c->content, "MPD_API_GET_BROWSE,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
-                n = mpd_put_browse(mpd.buf, p_charbuf);
-            else
-                n = mpd_put_browse(mpd.buf, "/");
-            free(p_charbuf);
+            if(sscanf(c->content, "MPD_API_GET_BROWSE,%u,%m[^\t\n]", &uint_buf, &p_charbuf) && p_charbuf != NULL)
+            {
+                n = mpd_put_browse(mpd.buf, p_charbuf, uint_buf);
+                free(p_charbuf);
+            }
             break;
         case MPD_API_ADD_TRACK:
             if(sscanf(c->content, "MPD_API_ADD_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
@@ -177,7 +179,6 @@ int callback_mpd(struct mg_connection *c)
 
                 mpd.password = p_charbuf;
                 mpd.conn_state = MPD_RECONNECT;
-                printf("Got mpd pw %s\n", mpd.password);
                 return MG_CLIENT_CONTINUE;
             }
             break;
@@ -309,21 +310,13 @@ void mpd_poll(struct mg_server *s)
 
 char* mpd_get_title(struct mpd_song const *song)
 {
-    char *str, *ptr;
+    char *str;
 
     str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
     if(str == NULL){
         str = basename((char *)mpd_song_get_uri(song));
     }
 
-    if(str == NULL)
-        return NULL;
-
-    ptr = str;
-    while(*ptr++ != '\0')
-        if(*ptr=='"')
-            *ptr='\'';
-
     return str;
 }
 
@@ -373,106 +366,131 @@ int mpd_put_current_song(char *buffer)
     if(song == NULL)
         return 0;
 
-    cur += snprintf(cur, end - cur, "{\"type\": \"song_change\", \"data\":"
-            "{\"pos\":%d, \"title\":\"%s\"",
-            mpd_song_get_pos(song),
-            mpd_get_title(song));
+    cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
+    cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
+    cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
+    cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
+
     if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
-        cur += snprintf(cur, end - cur, ", \"artist\":\"%s\"",
-            mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
+    {
+        cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
+        cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
+    }
+
     if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
-        cur += snprintf(cur, end - cur, ", \"album\":\"%s\"",
-            mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
+    {
+        cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
+        cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
+    }
 
-    cur += snprintf(cur, end - cur, "}}");
+    cur += json_emit_raw_str(cur, end - cur, "}}");
     mpd_song_free(song);
     mpd_response_finish(mpd.conn);
 
     return cur - buffer;
 }
 
-int mpd_put_queue(char *buffer)
+int mpd_put_queue(char *buffer, unsigned int offset)
 {
     char *cur = buffer;
     const char *end = buffer + MAX_SIZE;
     struct mpd_entity *entity;
 
-    if (!mpd_send_list_queue_meta(mpd.conn))
+    if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE))
         RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
 
-    cur += snprintf(cur, end  - cur, "{\"type\": \"queue\", \"data\": [ ");
+    cur += json_emit_raw_str(cur, end  - cur, "{\"type\":\"queue\",\"data\":[ ");
 
     while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
         const struct mpd_song *song;
 
         if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
             song = mpd_entity_get_song(entity);
-            cur += snprintf(cur, end  - cur, 
-                "{\"id\":%d, \"pos\":%d, \"duration\":%d, \"title\":\"%s\"},",
-                mpd_song_get_id(song),
-                mpd_song_get_pos(song),
-                mpd_song_get_duration(song),
-                mpd_get_title(song)
-                );
+
+            cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
+            cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
+            cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
+            cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
+            cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
+            cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
+            cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
+            cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
+            cur += json_emit_raw_str(cur, end - cur, "},");
         }
         mpd_entity_free(entity);
     }
 
     /* remove last ',' */
     cur--;
-    cur += snprintf(cur, end  - cur, "] }");
+
+    cur += json_emit_raw_str(cur, end - cur, "]}");
     return cur - buffer;
 }
 
-int mpd_put_browse(char *buffer, char *path)
+int mpd_put_browse(char *buffer, char *path, unsigned int offset)
 {
     char *cur = buffer;
     const char *end = buffer + MAX_SIZE;
     struct mpd_entity *entity;
-
+    unsigned int entity_count = 0;
 
     if (!mpd_send_list_meta(mpd.conn, path))
         RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
 
-    cur += snprintf(cur, end  - cur, "{\"type\":\"browse\",\"data\":[ ");
+    cur += json_emit_raw_str(cur, end  - cur, "{\"type\":\"browse\",\"data\":[ ");
 
     while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
         const struct mpd_song *song;
         const struct mpd_directory *dir;
         const struct mpd_playlist *pl;
 
+        if(offset > entity_count)
+        {
+            mpd_entity_free(entity);
+            entity_count++;
+            continue;
+        }
+        else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
+        {
+            mpd_entity_free(entity);
+            cur += json_emit_raw_str(cur, end  - cur, "{\"type\":\"wrap\",\"count\":");
+            cur += json_emit_int(cur, end - cur, entity_count);
+            cur += json_emit_raw_str(cur, end  - cur, "} ");
+            break;
+        }
+
         switch (mpd_entity_get_type(entity)) {
             case MPD_ENTITY_TYPE_UNKNOWN:
                 break;
 
             case MPD_ENTITY_TYPE_SONG:
                 song = mpd_entity_get_song(entity);
-                cur += snprintf(cur, end  - cur, 
-                        "{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
-                        mpd_song_get_uri(song),
-                        mpd_song_get_duration(song),
-                        mpd_get_title(song)
-                        );
+                cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
+                cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
+                cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
+                cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
+                cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
+                cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
+                cur += json_emit_raw_str(cur, end - cur, "},");
                 break;
 
             case MPD_ENTITY_TYPE_DIRECTORY:
                 dir = mpd_entity_get_directory(entity);
-                cur += snprintf(cur, end  - cur, 
-                        "{\"type\":\"directory\",\"dir\":\"%s\", \"basename\":\"%s\"},",
-                        mpd_directory_get_path(dir), 
-                        basename((char *)mpd_directory_get_path(dir))
-                        );
+
+                cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"directory\",\"dir\":");
+                cur += json_emit_quoted_str(cur, end - cur, mpd_directory_get_path(dir));
+                cur += json_emit_raw_str(cur, end - cur, "},");
                 break;
 
             case MPD_ENTITY_TYPE_PLAYLIST:
                 pl = mpd_entity_get_playlist(entity);
-                cur += snprintf(cur, end  - cur, 
-                        "{\"type\":\"playlist\",\"plist\":\"%s\"},",
-                        mpd_playlist_get_path(pl)
-                        );
+                cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"playlist\",\"plist\":");
+                cur += json_emit_quoted_str(cur, end - cur, mpd_playlist_get_path(pl));
+                cur += json_emit_raw_str(cur, end - cur, "},");
                 break;
         }
         mpd_entity_free(entity);
+        entity_count++;
     }
 
     if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
@@ -483,12 +501,14 @@ int mpd_put_browse(char *buffer, char *path)
 
     /* remove last ',' */
     cur--;
-    cur += snprintf(cur, end  - cur, "] }");
+
+    cur += json_emit_raw_str(cur, end - cur, "]}");
     return cur - buffer;
 }
 
 int mpd_search(char *buffer, char *searchstr)
 {
+    int i = 0;
     char *cur = buffer;
     const char *end = buffer + MAX_SIZE;
     struct mpd_song *song;
@@ -500,21 +520,30 @@ int mpd_search(char *buffer, char *searchstr)
     else if(mpd_search_commit(mpd.conn) == false)
         RETURN_ERROR_AND_RECOVER("mpd_search_commit");
     else {
-        cur += snprintf(cur, end  - cur, "{\"type\": \"search\", \"data\": [ ");
+        cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ ");
 
         while((song = mpd_recv_song(mpd.conn)) != NULL) {
-            cur += snprintf(cur, end  - cur, 
-                "{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
-                mpd_song_get_uri(song),
-                mpd_song_get_duration(song),
-                mpd_get_title(song)
-            );
+            cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
+            cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
+            cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
+            cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
+            cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
+            cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
+            cur += json_emit_raw_str(cur, end - cur, "},");
             mpd_song_free(song);
+
+            /* Maximum results */
+            if(i++ >= 300)
+            {
+                cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
+                break;
+            }
         }
 
         /* remove last ',' */
         cur--;
-        cur += snprintf(cur, end  - cur, "] }");
+
+        cur += json_emit_raw_str(cur, end - cur, "]}");
     }
     return cur - buffer;
 }

+ 4 - 2
src/mpd_client.h

@@ -32,6 +32,8 @@
 
 
 #define MAX_SIZE 1024 * 100
+#define MAX_ELEMENTS_PER_PAGE 512
+
 #define GEN_ENUM(X) X,
 #define GEN_STR(X) #X,
 #define MPD_CMDS(X) \
@@ -98,8 +100,8 @@ int callback_mpd(struct mg_connection *c);
 int mpd_close_handler(struct mg_connection *c);
 int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version);
 int mpd_put_current_song(char *buffer);
-int mpd_put_queue(char *buffer);
-int mpd_put_browse(char *buffer, char *path);
+int mpd_put_queue(char *buffer, unsigned int offset);
+int mpd_put_browse(char *buffer, char *path, unsigned int offset);
 int mpd_search(char *buffer, char *searchstr);
 void mpd_disconnect();
 #endif

+ 10 - 9
src/ympd.c

@@ -67,12 +67,12 @@ int main(int argc, char **argv)
         {"port",         required_argument, 0, 'p'},
         {"webport",      required_argument, 0, 'w'},
         {"user",         required_argument, 0, 'u'},
-        {"version",      no_argument,       0, 'V'},
+        {"version",      no_argument,       0, 'v'},
         {"help",         no_argument,       0,  0 },
         {0,              0,                 0,  0 }
     };
 
-    while((n = getopt_long(argc, argv, "h:p:w:u::V",
+    while((n = getopt_long(argc, argv, "h:p:w:u:v",
                 long_options, &option_index)) != -1) {
         switch (n) {
             case 'h':
@@ -84,9 +84,10 @@ int main(int argc, char **argv)
                 mg_set_option(server, "listening_port", optarg);
                 break;
             case 'u':
+                printf("Strarg is %s\n", optarg);
                 mg_set_option(server, "run_as_user", optarg);
                 break;
-            case 'V':
+            case 'v':
                 fprintf(stdout, "ympd  %d.%d.%d\n"
                         "Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
                         "built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
@@ -95,12 +96,12 @@ int main(int argc, char **argv)
                 break;
             default:
                 fprintf(stderr, "Usage: %s [OPTION]...\n\n"
-                        "\t-h, --host <host>\t\tconnect to mpd at host [localhost]\n"
-                        "\t-p, --port <port>\t\tconnect to mpd at port [6600]\n"
-                        "\t-w, --webport [ip:]<port>\t\tlisten interface/port for webserver [8080]\n"
-                        "\t-u, --user <username>\t\t\tdrop priviliges to user after socket bind\n"
-                        "\t-V, --version\t\t\tget version\n"
-                        "\t--help\t\t\t\tthis help\n"
+                        " -h, --host <host>\t\tconnect to mpd at host [localhost]\n"
+                        " -p, --port <port>\t\tconnect to mpd at port [6600]\n"
+                        " -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n"
+                        " -u, --user <username>\t\tdrop priviliges to user after socket bind\n"
+                        " -V, --version\t\t\tget version\n"
+                        " --help\t\t\t\tthis help\n"
                         , argv[0]);
                 return EXIT_FAILURE;
         }