From c11bfa0d1adeaf25bced0c87d21619b49b5d3fc2 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Thu, 21 Jun 2018 17:57:40 +0100 Subject: [PATCH] Save myMPD settings in state file, not in cookies --- htdocs/index.html | 1 - htdocs/js/js.cookie-2.2.0.js | 165 --------- htdocs/js/js.cookie-2.2.0.min.js | 3 - htdocs/js/mpd.js | 188 +++++----- htdocs/player.html | 4 +- src/frozen/frozen.c | 566 +++++++++++++++++++++++++++---- src/frozen/frozen.h | 129 ++++++- src/mpd_client.c | 45 ++- src/mpd_client.h | 6 +- 9 files changed, 716 insertions(+), 391 deletions(-) delete mode 100644 htdocs/js/js.cookie-2.2.0.js delete mode 100644 htdocs/js/js.cookie-2.2.0.min.js diff --git a/htdocs/index.html b/htdocs/index.html index 4949f4c..e03973a 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -687,7 +687,6 @@ - diff --git a/htdocs/js/js.cookie-2.2.0.js b/htdocs/js/js.cookie-2.2.0.js deleted file mode 100644 index 9a0945e..0000000 --- a/htdocs/js/js.cookie-2.2.0.js +++ /dev/null @@ -1,165 +0,0 @@ -/*! - * JavaScript Cookie v2.2.0 - * https://github.com/js-cookie/js-cookie - * - * Copyright 2006, 2015 Klaus Hartl & Fagner Brack - * Released under the MIT license - */ -;(function (factory) { - var registeredInModuleLoader = false; - if (typeof define === 'function' && define.amd) { - define(factory); - registeredInModuleLoader = true; - } - if (typeof exports === 'object') { - module.exports = factory(); - registeredInModuleLoader = true; - } - if (!registeredInModuleLoader) { - var OldCookies = window.Cookies; - var api = window.Cookies = factory(); - api.noConflict = function () { - window.Cookies = OldCookies; - return api; - }; - } -}(function () { - function extend () { - var i = 0; - var result = {}; - for (; i < arguments.length; i++) { - var attributes = arguments[ i ]; - for (var key in attributes) { - result[key] = attributes[key]; - } - } - return result; - } - - function init (converter) { - function api (key, value, attributes) { - var result; - if (typeof document === 'undefined') { - return; - } - - // Write - - if (arguments.length > 1) { - attributes = extend({ - path: '/' - }, api.defaults, attributes); - - if (typeof attributes.expires === 'number') { - var expires = new Date(); - expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); - attributes.expires = expires; - } - - // We're using "expires" because "max-age" is not supported by IE - attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; - - try { - result = JSON.stringify(value); - if (/^[\{\[]/.test(result)) { - value = result; - } - } catch (e) {} - - if (!converter.write) { - value = encodeURIComponent(String(value)) - .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); - } else { - value = converter.write(value, key); - } - - key = encodeURIComponent(String(key)); - key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); - key = key.replace(/[\(\)]/g, escape); - - var stringifiedAttributes = ''; - - for (var attributeName in attributes) { - if (!attributes[attributeName]) { - continue; - } - stringifiedAttributes += '; ' + attributeName; - if (attributes[attributeName] === true) { - continue; - } - stringifiedAttributes += '=' + attributes[attributeName]; - } - return (document.cookie = key + '=' + value + stringifiedAttributes); - } - - // Read - - if (!key) { - result = {}; - } - - // To prevent the for loop in the first place assign an empty array - // in case there are no cookies at all. Also prevents odd result when - // calling "get()" - var cookies = document.cookie ? document.cookie.split('; ') : []; - var rdecode = /(%[0-9A-Z]{2})+/g; - var i = 0; - - for (; i < cookies.length; i++) { - var parts = cookies[i].split('='); - var cookie = parts.slice(1).join('='); - - if (!this.json && cookie.charAt(0) === '"') { - cookie = cookie.slice(1, -1); - } - - try { - var name = parts[0].replace(rdecode, decodeURIComponent); - cookie = converter.read ? - converter.read(cookie, name) : converter(cookie, name) || - cookie.replace(rdecode, decodeURIComponent); - - if (this.json) { - try { - cookie = JSON.parse(cookie); - } catch (e) {} - } - - if (key === name) { - result = cookie; - break; - } - - if (!key) { - result[name] = cookie; - } - } catch (e) {} - } - - return result; - } - - api.set = api; - api.get = function (key) { - return api.call(api, key); - }; - api.getJSON = function () { - return api.apply({ - json: true - }, [].slice.call(arguments)); - }; - api.defaults = {}; - - api.remove = function (key, attributes) { - api(key, '', extend(attributes, { - expires: -1 - })); - }; - - api.withConverter = init; - - return api; - } - - return init(function () {}); -})); diff --git a/htdocs/js/js.cookie-2.2.0.min.js b/htdocs/js/js.cookie-2.2.0.min.js deleted file mode 100644 index 69fecbb..0000000 --- a/htdocs/js/js.cookie-2.2.0.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! js-cookie v2.2.0 | MIT */ - -!function(e){var n=!1;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var o=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=o,t}}}(function(){function e(){for(var e=0,n={};e1){if("number"==typeof(i=e({path:"/"},t.defaults,i)).expires){var a=new Date;a.setMilliseconds(a.getMilliseconds()+864e5*i.expires),i.expires=a}i.expires=i.expires?i.expires.toUTCString():"";try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(e){}r=o.write?o.write(r,n):encodeURIComponent(r+"").replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=(n=(n=encodeURIComponent(n+"")).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent)).replace(/[\(\)]/g,escape);var s="";for(var f in i)i[f]&&(s+="; "+f,!0!==i[f]&&(s+="="+i[f]));return document.cookie=n+"="+r+s}n||(c={});for(var p=document.cookie?document.cookie.split("; "):[],d=/(%[0-9A-Z]{2})+/g,u=0;u input').focus(); }); - if(!notificationsSupported()) - $('#btnnotifyWeb').addClass("disabled"); - else - if (Cookies.get('notificationWeb') === 'true') - $('#btnnotifyWeb').removeClass('btn-secondary').addClass("btn-success") - - if (Cookies.get('notificationPage') === 'true') - $('#btnnotifyPage').removeClass('btn-secondary').addClass("btn-success") + + + add_filter('#BrowseFilesystemFilterLetters'); add_filter('#BrowseDatabaseFilterLetters'); @@ -311,9 +304,6 @@ function webSocketConnect() { case "song_change": songChange(obj); break; - case 'settings': - parseSettings(obj); - break; case 'error': showNotification(obj.data,'','','danger'); default: @@ -373,55 +363,73 @@ function parseStats(obj) { $('#mpdVersion').text(obj.data.mpd_version); } -function parseMympdSettings(obj) { - settings.notifyPage=obj.data.notifyPage; - settings.notifyWeb=obj.data.notifyWeb; - //try to set web notification -} - function parseSettings(obj) { - if (!isNaN(obj.data.max_elements_per_page)) - MAX_ELEMENTS_PER_PAGE=obj.data.max_elements_per_page; - - if(obj.data.random) - $('#btnrandom').removeClass('btn-secondary').addClass("btn-success") - else - $('#btnrandom').removeClass("btn-success").addClass("btn-secondary"); + if (obj.data.random) + $('#btnrandom').removeClass('btn-secondary').addClass("btn-success") + else + $('#btnrandom').removeClass("btn-success").addClass("btn-secondary"); - if(obj.data.consume) - $('#btnconsume').removeClass('btn-secondary').addClass("btn-success") - else - $('#btnconsume').removeClass("btn-success").addClass("btn-secondary"); + if (obj.data.consume) + $('#btnconsume').removeClass('btn-secondary').addClass("btn-success") + else + $('#btnconsume').removeClass("btn-success").addClass("btn-secondary"); - if(obj.data.single) - $('#btnsingle').removeClass('btn-secondary').addClass("btn-success") - else - $('#btnsingle').removeClass("btn-success").addClass("btn-secondary"); + if (obj.data.single) + $('#btnsingle').removeClass('btn-secondary').addClass("btn-success") + else + $('#btnsingle').removeClass("btn-success").addClass("btn-secondary"); - if(obj.data.crossfade != undefined) - $('#inputCrossfade').removeAttr('disabled').val(obj.data.crossfade); - else - $('#inputCrossfade').attr('disabled', 'disabled'); + if (obj.data.crossfade != undefined) + $('#inputCrossfade').removeAttr('disabled').val(obj.data.crossfade); + else + $('#inputCrossfade').attr('disabled', 'disabled'); - if(obj.data.mixrampdb != undefined) - $('#inputMixrampdb').removeAttr('disabled').val(obj.data.mixrampdb); - else - $('#inputMixrampdb').attr('disabled', 'disabled'); + if (obj.data.mixrampdb != undefined) + $('#inputMixrampdb').removeAttr('disabled').val(obj.data.mixrampdb); + else + $('#inputMixrampdb').attr('disabled', 'disabled'); - if(obj.data.mixrampdelay != undefined) - $('#inputMixrampdelay').removeAttr('disabled').val(obj.data.mixrampdelay); - else - $('#inputMixrampdb').attr('disabled', 'disabled'); + if (obj.data.mixrampdelay != undefined) + $('#inputMixrampdelay').removeAttr('disabled').val(obj.data.mixrampdelay); + else + $('#inputMixrampdb').attr('disabled', 'disabled'); - if(obj.data.repeat) - $('#btnrepeat').removeClass('btn-secondary').addClass("btn-success") - else - $('#btnrepeat').removeClass("btn-success").addClass("btn-secondary"); + if (obj.data.repeat) + $('#btnrepeat').removeClass('btn-secondary').addClass("btn-success") + else + $('#btnrepeat').removeClass("btn-success").addClass("btn-secondary"); - $("#selectReplaygain").val(obj.data.replaygain); + $("#selectReplaygain").val(obj.data.replaygain); + if (notificationsSupported()) { + if (obj.data.notificationWeb) { + $('#btnnotifyWeb').removeClass('btn-secondary').addClass("btn-success") + + Notification.requestPermission(function (permission) { + if(!('permission' in Notification)) + Notification.permission = permission; + if (permission === 'granted') { + $('#btnnotifyWeb').removeClass('btn-secondary').addClass('btn-success'); + } else { + $('#btnnotifyWeb').addClass('btn-secondary').removeClass('btn-success'); + obj.data.notificationWeb=0; + } + }); + } + else + $('#btnnotifyWeb').addClass('btn-secondary').removeClass("btn-success"); + } else { + $('#btnnotifyWeb').addClass("disabled"); + $('#btnnotifyWeb').addClass('btn-secondary').removeClass("btn-success"); + } + + if (obj.data.notificationPage) + $('#btnnotifyPage').removeClass('btn-secondary').addClass("btn-success") + else + $('#btnnotifyPage').addClass('btn-secondary').removeClass("btn-success"); + + settings=obj.data; setLocalStream(obj.data.mpdhost,obj.data.streamport); - coverImageFile=obj.data.coverimage; } function parseOutputnames(obj) { @@ -824,7 +832,7 @@ function parseListDBtags(obj) { function parseListTitles(obj) { if(app.current.app !== 'Browse' && app.current.tab !== 'Database' && app.current.view !== 'Album') return; var album=$('#'+genId(obj.album)+' > div > table > tbody'); - $('#'+genId(obj.album)+' > img').attr('src','/library/'+obj.data[0].uri.replace(/\/[^\/]+$/,'\/')+coverImageFile); + $('#'+genId(obj.album)+' > img').attr('src','/library/'+obj.data[0].uri.replace(/\/[^\/]+$/,'\/')+settings.coverimage); $('#'+genId(obj.album)+' > img').attr('uri',obj.data[0].uri.replace(/\/[^\/]+$/,'')); $('#'+genId(obj.album)+' > img').attr('data-album',encodeURI(obj.album)); var titleList=''; @@ -850,26 +858,26 @@ function parseListTitles(obj) { } function setPagination(number) { - var totalPages=Math.ceil(number / MAX_ELEMENTS_PER_PAGE); + var totalPages=Math.ceil(number / settings.max_elements_per_page); var cat=app.current.app+(app.current.tab==undefined ? '': app.current.tab); if (totalPages==0) { totalPages=1; } - $('#'+cat+'PaginationTopPage').text('Page '+(app.current.page / MAX_ELEMENTS_PER_PAGE + 1)+' / '+totalPages); - $('#'+cat+'PaginationBottomPage').text('Page '+(app.current.page / MAX_ELEMENTS_PER_PAGE + 1)+' / '+totalPages); + $('#'+cat+'PaginationTopPage').text('Page '+(app.current.page / settings.max_elements_per_page + 1)+' / '+totalPages); + $('#'+cat+'PaginationBottomPage').text('Page '+(app.current.page / settings.max_elements_per_page + 1)+' / '+totalPages); if (totalPages > 1) { $('#'+cat+'PaginationTopPage').removeClass('disabled').removeAttr('disabled'); $('#'+cat+'PaginationBottomPage').removeClass('disabled').removeAttr('disabled'); $('#'+cat+'PaginationTopPages').empty(); $('#'+cat+'PaginationBottomPages').empty(); for (var i=0;i'+(i+1)+''); - $('#'+cat+'PaginationBottomPages').append(''); + $('#'+cat+'PaginationTopPages').append(''); + $('#'+cat+'PaginationBottomPages').append(''); } } else { $('#'+cat+'PaginationTopPage').addClass('disabled').attr('disabled','disabled'); $('#'+cat+'PaginationBottomPage').addClass('disabled').attr('disabled','disabled'); } - if(number > app.current.page + MAX_ELEMENTS_PER_PAGE) { + if(number > app.current.page + settings.max_elements_per_page) { $('#'+cat+'PaginationTopNext').removeClass('disabled').removeAttr('disabled'); $('#'+cat+'PaginationBottomNext').removeClass('disabled').removeAttr('disabled'); $('#'+cat+'ButtonsBottom').removeClass('hide'); @@ -1013,10 +1021,16 @@ $('#btnconsume').on('click', function (e) { $('#btnsingle').on('click', function (e) { toggleBtn(this); }); - $('#btnrepeat').on('click', function (e) { toggleBtn(this); }); +$('#btnnotifyPage').on('click', function (e) { + toggleBtn(this); +}); +$('#btnnotifyWeb').on('click', function (e) { + toggleBtn(this); +}); + $('#cardBrowseNavFilesystem').on('click', function (e) { app.goto('Browse','Filesystem'); @@ -1100,7 +1114,7 @@ function confirmSettings() { } } if (formOK == true) { - sendAPI({"cmd":"MPD_API_SET_MPD_SETTINGS", "data": { + sendAPI({"cmd":"MPD_API_SET_SETTINGS", "data": { "consume": ($('#btnconsume').hasClass('btn-success') ? 1 : 0), "random": ($('#btnrandom').hasClass('btn-success') ? 1 : 0), "single": ($('#btnsingle').hasClass('btn-success') ? 1 : 0), @@ -1108,11 +1122,9 @@ function confirmSettings() { "replaygain": $('#selectReplaygain').val(), "crossfade": $('#inputCrossfade').val(), "mixrampdb": $('#inputMixrampdb').val(), - "mixrampdelay": $('#inputMixrampdelay').val() - }}); - sendAPI({"cmd":"MPD_API_SET_MYMPD_SETTINGS","data": { - "notificationsWeb": ($('#btnnotifyWeb').hasClass('btn-success') ? 1 : 0), - "notificationsPage": ($('#btnnotifyPage').hasClass('btn-success') ? 1 : 0) + "mixrampdelay": $('#inputMixrampdelay').val(), + "notificationWeb": ($('#btnnotifyWeb').hasClass('btn-success') ? 1 : 0), + "notificationPage": ($('#btnnotifyPage').hasClass('btn-success') ? 1 : 0) }}); $('#settings').modal('hide'); } @@ -1127,33 +1139,7 @@ $('#trashmodebtns > button').on('click', function(e) { $(this).removeClass("btn-secondary").addClass("btn-success"); }); -$('#btnnotifyWeb').on('click', function (e) { - if(Cookies.get('notificationWeb') === 'true') { - Cookies.set('notificationWeb', false, { expires: 424242 }); - $('#btnnotifyWeb').removeClass('btn-success').addClass('btn-secondary'); - } else { - Notification.requestPermission(function (permission) { - if(!('permission' in Notification)) { - Notification.permission = permission; - } - - if (permission === 'granted') { - Cookies.set('notificationWeb', true, { expires: 424242 }); - $('#btnnotifyWeb').removeClass('btn-secondary').addClass('btn-success'); - } - }); - } -}); - -$('#btnnotifyPage').on('click', function (e) { - if(Cookies.get("notificationPage") === 'true') { - Cookies.set("notificationPage", false, { expires: 424242 }); - $('#btnnotifyPage').removeClass('btn-success').addClass('btn-secondary'); - } else { - Cookies.set('notificationPage', true, { expires: 424242 }); - $('#btnnotifyPage').removeClass('btn-secondary').addClass('btn-success'); - } -}); + $('#search > input').keypress(function (event) { if ( event.which == 13 ) { @@ -1221,10 +1207,10 @@ function scrollToTop() { function gotoPage(x,element,event) { switch (x) { case "next": - app.current.page += MAX_ELEMENTS_PER_PAGE; + app.current.page += settings.max_elements_per_page; break; case "prev": - app.current.page -= MAX_ELEMENTS_PER_PAGE; + app.current.page -= settings.max_elements_per_page; if(app.current.page <= 0) app.current.page = 0; break; @@ -1251,13 +1237,13 @@ function saveQueue() { } function showNotification(notificationTitle,notificationText,notificationHtml,notificationType) { - if (Cookies.get('notificationWeb') === 'true') { + if (settings.notificationWeb == 1) { var notification = new Notification(notificationTitle, {icon: 'assets/favicon.ico', body: notificationText}); setTimeout(function(notification) { notification.close(); }, 3000, notification); } - if (Cookies.get('notificationPage') === 'true') { + if (settings.notificationPage == 1) { $.notify({ title: notificationTitle, message: notificationHtml},{ type: notificationType, offset: { y: 60, x:20 }, template: ' - - - + diff --git a/src/frozen/frozen.c b/src/frozen/frozen.c index 4b5e279..209a6bb 100644 --- a/src/frozen/frozen.c +++ b/src/frozen/frozen.c @@ -1,20 +1,19 @@ /* * Copyright (c) 2004-2013 Sergey Lyubka - * Copyright (c) 2013 Cesanta Software Limited + * Copyright (c) 2018 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 . + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Alternatively, you can license this library under a commercial - * license, as set out in . + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */ @@ -35,6 +34,8 @@ #endif #ifdef _WIN32 +#undef snprintf +#undef vsnprintf #define snprintf cs_win_snprintf #define vsnprintf cs_win_vsnprintf int cs_win_snprintf(char *str, size_t size, const char *format, ...); @@ -47,33 +48,27 @@ typedef unsigned _int64 uint64_t; #endif #define PRId64 "I64d" #define PRIu64 "I64u" -#if !defined(SIZE_T_FMT) -#if _MSC_VER >= 1310 -#define SIZE_T_FMT "Iu" -#else -#define SIZE_T_FMT "u" -#endif -#endif #else /* _WIN32 */ /* wants this for C++ */ #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include -#if !defined(SIZE_T_FMT) -#define SIZE_T_FMT "zu" -#endif #endif /* _WIN32 */ +#ifndef INT64_FMT #define INT64_FMT PRId64 +#endif +#ifndef UINT64_FMT #define UINT64_FMT PRIu64 +#endif #ifndef va_copy #define va_copy(x, y) x = y #endif #ifndef JSON_MAX_PATH_LEN -#define JSON_MAX_PATH_LEN 60 +#define JSON_MAX_PATH_LEN 256 #endif struct frozen { @@ -85,14 +80,14 @@ struct frozen { /* For callback API */ char path[JSON_MAX_PATH_LEN]; - int path_len; + size_t path_len; void *callback_data; json_walk_callback_t callback; }; struct fstate { const char *ptr; - int path_len; + size_t path_len; }; #define SET_STATE(fr, ptr, str, len) \ @@ -117,14 +112,15 @@ struct fstate { static int append_to_path(struct frozen *f, const char *str, int size) { int n = f->path_len; - f->path_len += - snprintf(f->path + f->path_len, sizeof(f->path) - (f->path_len + 1), - "%.*s", size, str); - + int left = sizeof(f->path) - n - 1; + if (size > left) size = left; + memcpy(f->path + n, str, size); + f->path[n + size] = '\0'; + f->path_len += size; return n; } -static void truncate_path(struct frozen *f, int len) { +static void truncate_path(struct frozen *f, size_t len) { f->path_len = len; f->path[len] = '\0'; } @@ -242,7 +238,7 @@ static int parse_string(struct frozen *f) { ch = *(unsigned char *) f->cur; len = get_utf8_char_len((unsigned char) ch); EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ - EXPECT(len < left(f), JSON_STRING_INCOMPLETE); + EXPECT(len <= left(f), JSON_STRING_INCOMPLETE); if (ch == '\\') { EXPECT((n = get_escape_len(f->cur + 1, left(f))) > 0, n); len += n; @@ -288,9 +284,9 @@ static int parse_number(struct frozen *f) { static int parse_array(struct frozen *f) { int i = 0, current_path_len; char buf[20]; + CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); TRY(test_and_skip(f, '[')); { - CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); { SET_STATE(f, f->cur - 1, "", 0); while (cur(f) != ']') { @@ -374,9 +370,6 @@ static int parse_value(struct frozen *f) { /* key = identifier | string */ static int parse_key(struct frozen *f) { int ch = cur(f); -#if 0 - printf("%s [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur); -#endif if (is_alpha(ch)) { TRY(parse_identifier(f)); } else if (ch == '"') { @@ -407,19 +400,17 @@ static int parse_pair(struct frozen *f) { /* object = '{' pair { ',' pair } '}' */ static int parse_object(struct frozen *f) { + CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); TRY(test_and_skip(f, '{')); { - CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); - { - SET_STATE(f, f->cur - 1, ".", 1); - while (cur(f) != '}') { - TRY(parse_pair(f)); - if (cur(f) == ',') f->cur++; - } - TRY(test_and_skip(f, '}')); - truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); + SET_STATE(f, f->cur - 1, ".", 1); + while (cur(f) != '}') { + TRY(parse_pair(f)); + if (cur(f) == ',') f->cur++; } + TRY(test_and_skip(f, '}')); + truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); } return 0; } @@ -430,7 +421,8 @@ static int doit(struct frozen *f) { return parse_value(f); } -static int json_encode_string(struct json_out *out, const char *p, size_t len) { +int json_escape(struct json_out *out, const char *p, size_t len) WEAK; +int json_escape(struct json_out *out, const char *p, size_t len) { size_t i, cl, n = 0; const char *hex_digits = "0123456789abcdef"; const char *specials = "btnvfr"; @@ -505,7 +497,7 @@ static int b64rev(int c) { } } -static uint8_t hexdec(const char *s) { +static unsigned char hexdec(const char *s) { #define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') int a = tolower(*(const unsigned char *) s); int b = tolower(*(const unsigned char *) (s + 1)); @@ -569,7 +561,7 @@ int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { skip += 2; } else if (fmt[1] == 'z' && fmt[2] == 'u') { size_t val = va_arg(ap, size_t); - snprintf(buf, sizeof(buf), "%" SIZE_T_FMT, val); + snprintf(buf, sizeof(buf), "%lu", (unsigned long) val); len += out->printer(out, buf, strlen(buf)); skip += 1; } else if (fmt[1] == 'M') { @@ -613,7 +605,7 @@ int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { l = strlen(p); } len += out->printer(out, quote, 1); - len += json_encode_string(out, p, l); + len += json_escape(out, p, l); len += out->printer(out, quote, 1); } } else { @@ -628,30 +620,48 @@ int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { * TODO(dfrank): reimplement %s and %.*s in order to avoid that. */ - const char *end_of_format_specifier = "sdfFgGlhuI.*-0123456789"; - size_t n = strspn(fmt + 1, end_of_format_specifier); + const char *end_of_format_specifier = "sdfFgGlhuIcx.*-0123456789"; + int n = strspn(fmt + 1, end_of_format_specifier); char *pbuf = buf; - size_t need_len; + int need_len, size = sizeof(buf); char fmt2[20]; - va_list sub_ap; - strncpy(fmt2, fmt, n + 1 > sizeof(fmt2) ? sizeof(fmt2) : n + 1); + va_list ap_copy; + strncpy(fmt2, fmt, + n + 1 > (int) sizeof(fmt2) ? sizeof(fmt2) : (size_t) n + 1); fmt2[n + 1] = '\0'; - va_copy(sub_ap, ap); - need_len = - vsnprintf(buf, sizeof(buf), fmt2, sub_ap) + 1 /* null-term */; - /* - * TODO(lsm): Fix windows & eCos code path here. Their vsnprintf - * implementation returns -1 on overflow rather needed size. - */ - if (need_len > sizeof(buf)) { + va_copy(ap_copy, ap); + need_len = vsnprintf(pbuf, size, fmt2, ap_copy); + va_end(ap_copy); + + if (need_len < 0) { + /* + * Windows & eCos vsnprintf implementation return -1 on overflow + * instead of needed size. + */ + pbuf = NULL; + while (need_len < 0) { + free(pbuf); + size *= 2; + if ((pbuf = (char *) malloc(size)) == NULL) break; + va_copy(ap_copy, ap); + need_len = vsnprintf(pbuf, size, fmt2, ap_copy); + va_end(ap_copy); + } + } else if (need_len >= (int) sizeof(buf)) { /* * resulting string doesn't fit into a stack-allocated buffer `buf`, * so we need to allocate a new buffer from heap and use it */ - pbuf = (char *) malloc(need_len); - va_copy(sub_ap, ap); - vsnprintf(pbuf, need_len, fmt2, sub_ap); + if ((pbuf = (char *) malloc(need_len + 1)) != NULL) { + va_copy(ap_copy, ap); + vsnprintf(pbuf, need_len + 1, fmt2, ap_copy); + va_end(ap_copy); + } + } + if (pbuf == NULL) { + buf[0] = '\0'; + pbuf = buf; } /* @@ -663,7 +673,6 @@ int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { if ((n + 1 == strlen("%" PRId64) && strcmp(fmt2, "%" PRId64) == 0) || (n + 1 == strlen("%" PRIu64) && strcmp(fmt2, "%" PRIu64) == 0)) { (void) va_arg(ap, int64_t); - skip += strlen(PRIu64) - 1; } else if (strcmp(fmt2, "%.*s") == 0) { (void) va_arg(ap, int); (void) va_arg(ap, char *); @@ -704,6 +713,7 @@ int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { } len += out->printer(out, quote, 1); } else { + len += out->printer(out, fmt, 1); fmt++; } } @@ -749,7 +759,8 @@ int json_printf_array(struct json_out *out, va_list *ap) { } #ifdef _WIN32 -int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap) WEAK; +int cs_win_vsnprintf(char *str, size_t size, const char *format, + va_list ap) WEAK; int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap) { int res = _vsnprintf(str, size, format, ap); va_end(ap); @@ -788,6 +799,7 @@ int json_walk(const char *json_string, int json_string_length, } struct scan_array_info { + int found; char path[JSON_MAX_PATH_LEN]; struct json_token *token; }; @@ -802,6 +814,7 @@ static void json_scanf_array_elem_cb(void *callback_data, const char *name, if (strcmp(path, info->path) == 0) { *info->token = *token; + info->found = 1; } } @@ -811,10 +824,11 @@ int json_scanf_array_elem(const char *s, int len, const char *path, int idx, struct json_token *token) { struct scan_array_info info; info.token = token; + info.found = 0; memset(token, 0, sizeof(*token)); snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx); json_walk(s, len, json_scanf_array_elem_cb, &info); - return token->len; + return info.found ? token->len : -1; } struct json_scanf_info { @@ -856,6 +870,7 @@ static void json_scanf_cb(void *callback_data, const char *name, size_t name_len, const char *path, const struct json_token *token) { struct json_scanf_info *info = (struct json_scanf_info *) callback_data; + char buf[32]; /* Must be enough to hold numbers */ (void) name; (void) name_len; @@ -876,7 +891,17 @@ static void json_scanf_cb(void *callback_data, const char *name, switch (info->type) { case 'B': info->num_conversions++; - *(int *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + switch (sizeof(bool)) { + case sizeof(char): + *(char *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + case sizeof(int): + *(int *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + default: + /* should never be here */ + abort(); + } break; case 'M': { union { @@ -889,12 +914,21 @@ static void json_scanf_cb(void *callback_data, const char *name, } case 'Q': { char **dst = (char **) info->target; - int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0); - if (unescaped_len >= 0 && - (*dst = (char *) malloc(unescaped_len + 1)) != NULL) { - info->num_conversions++; - json_unescape(token->ptr, token->len, *dst, unescaped_len); - (*dst)[unescaped_len] = '\0'; + if (token->type == JSON_TYPE_NULL) { + *dst = NULL; + } else { + int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0); + if (unescaped_len >= 0 && + (*dst = (char *) malloc(unescaped_len + 1)) != NULL) { + info->num_conversions++; + if (json_unescape(token->ptr, token->len, *dst, unescaped_len) == + unescaped_len) { + (*dst)[unescaped_len] = '\0'; + } else { + free(*dst); + *dst = NULL; + } + } } break; } @@ -927,7 +961,12 @@ static void json_scanf_cb(void *callback_data, const char *name, *(struct json_token *) info->target = *token; break; default: - info->num_conversions += sscanf(token->ptr, info->fmt, info->target); + /* Before scanf, copy into tmp buffer in order to 0-terminate it */ + if (token->len < (int) sizeof(buf)) { + memcpy(buf, token->ptr, token->len); + buf[token->len] = '\0'; + info->num_conversions += sscanf(buf, info->fmt, info->target); + } break; } } @@ -992,3 +1031,380 @@ int json_scanf(const char *str, int len, const char *fmt, ...) { va_end(ap); return result; } + +int json_vfprintf(const char *file_name, const char *fmt, va_list ap) WEAK; +int json_vfprintf(const char *file_name, const char *fmt, va_list ap) { + int res = -1; + FILE *fp = fopen(file_name, "wb"); + if (fp != NULL) { + struct json_out out = JSON_OUT_FILE(fp); + res = json_vprintf(&out, fmt, ap); + fputc('\n', fp); + fclose(fp); + } + return res; +} + +int json_fprintf(const char *file_name, const char *fmt, ...) WEAK; +int json_fprintf(const char *file_name, const char *fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = json_vfprintf(file_name, fmt, ap); + va_end(ap); + return result; +} + +char *json_fread(const char *path) WEAK; +char *json_fread(const char *path) { + FILE *fp; + char *data = NULL; + if ((fp = fopen(path, "rb")) == NULL) { + } else if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + } else { + long size = ftell(fp); + if (size > 0 && (data = (char *) malloc(size + 1)) != NULL) { + fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ + if (fread(data, 1, size, fp) != (size_t) size) { + free(data); + data = NULL; + } else { + data[size] = '\0'; + } + } + fclose(fp); + } + return data; +} + +struct json_setf_data { + const char *json_path; + const char *base; /* Pointer to the source JSON string */ + int matched; /* Matched part of json_path */ + int pos; /* Offset of the mutated value begin */ + int end; /* Offset of the mutated value end */ + int prev; /* Offset of the previous token end */ +}; + +static int get_matched_prefix_len(const char *s1, const char *s2) { + int i = 0; + while (s1[i] && s2[i] && s1[i] == s2[i]) i++; + return i; +} + +static void json_vsetf_cb(void *userdata, const char *name, size_t name_len, + const char *path, const struct json_token *t) { + struct json_setf_data *data = (struct json_setf_data *) userdata; + int off, len = get_matched_prefix_len(path, data->json_path); + if (t->ptr == NULL) return; + off = t->ptr - data->base; + // printf("--%d %s %d\n", t->type, path, off); + if (len > data->matched) data->matched = len; + + /* + * If there is no exact path match, set the mutation position to tbe end + * of the object or array + */ + if (len < data->matched && data->pos == 0 && + (t->type == JSON_TYPE_OBJECT_END || t->type == JSON_TYPE_ARRAY_END)) { + data->pos = data->end = data->prev; + } + + /* Exact path match. Set mutation position to the value of this token */ + if (strcmp(path, data->json_path) == 0 && t->type != JSON_TYPE_OBJECT_START && + t->type != JSON_TYPE_ARRAY_START) { + data->pos = off; + data->end = off + t->len; + } + + /* + * For deletion, we need to know where the previous value ends, because + * we don't know where matched value key starts. + * When the mutation position is not yet set, remember each value end. + * When the mutation position is already set, but it is at the beginning + * of the object/array, we catch the end of the object/array and see + * whether the object/array start is closer then previously stored prev. + */ + if (data->pos == 0) { + data->prev = off + t->len; /* pos is not yet set */ + } else if ((t->ptr[0] == '[' || t->ptr[0] == '{') && off + 1 < data->pos && + off + 1 > data->prev) { + data->prev = off + 1; + } + (void) name; + (void) name_len; +} + +int json_vsetf(const char *s, int len, struct json_out *out, + const char *json_path, const char *json_fmt, va_list ap) WEAK; +int json_vsetf(const char *s, int len, struct json_out *out, + const char *json_path, const char *json_fmt, va_list ap) { + struct json_setf_data data; + memset(&data, 0, sizeof(data)); + data.json_path = json_path; + data.base = s; + data.end = len; + // printf("S:[%.*s] %s %p\n", len, s, json_path, json_fmt); + json_walk(s, len, json_vsetf_cb, &data); + // printf("-> %d %d %d\n", data.prev, data.pos, data.end); + if (json_fmt == NULL) { + /* Deletion codepath */ + json_printf(out, "%.*s", data.prev, s); + /* Trim comma after the value that begins at object/array start */ + if (s[data.prev - 1] == '{' || s[data.prev - 1] == '[') { + int i = data.end; + while (i < len && is_space(s[i])) i++; + if (s[i] == ',') data.end = i + 1; /* Point after comma */ + } + json_printf(out, "%.*s", len - data.end, s + data.end); + } else { + /* Modification codepath */ + int n, off = data.matched, depth = 0; + + /* Print the unchanged beginning */ + json_printf(out, "%.*s", data.pos, s); + + /* Add missing keys */ + while ((n = strcspn(&json_path[off], ".[")) > 0) { + if (s[data.prev - 1] != '{' && s[data.prev - 1] != '[' && depth == 0) { + json_printf(out, ","); + } + if (off > 0 && json_path[off - 1] != '.') break; + json_printf(out, "%.*Q:", 1, json_path + off); + off += n; + if (json_path[off] != '\0') { + json_printf(out, "%c", json_path[off] == '.' ? '{' : '['); + depth++; + off++; + } + } + /* Print the new value */ + json_vprintf(out, json_fmt, ap); + + /* Close brackets/braces of the added missing keys */ + for (; off > data.matched; off--) { + int ch = json_path[off]; + const char *p = ch == '.' ? "}" : ch == '[' ? "]" : ""; + json_printf(out, "%s", p); + } + + /* Print the rest of the unchanged string */ + json_printf(out, "%.*s", len - data.end, s + data.end); + } + return data.end > data.pos ? 1 : 0; +} + +int json_setf(const char *s, int len, struct json_out *out, + const char *json_path, const char *json_fmt, ...) WEAK; +int json_setf(const char *s, int len, struct json_out *out, + const char *json_path, const char *json_fmt, ...) { + int result; + va_list ap; + va_start(ap, json_fmt); + result = json_vsetf(s, len, out, json_path, json_fmt, ap); + va_end(ap); + return result; +} + +struct prettify_data { + struct json_out *out; + int level; + int last_token; +}; + +static void indent(struct json_out *out, int level) { + while (level-- > 0) out->printer(out, " ", 2); +} + +static void print_key(struct prettify_data *pd, const char *path, + const char *name, int name_len) { + if (pd->last_token != JSON_TYPE_INVALID && + pd->last_token != JSON_TYPE_ARRAY_START && + pd->last_token != JSON_TYPE_OBJECT_START) { + pd->out->printer(pd->out, ",", 1); + } + if (path[0] != '\0') pd->out->printer(pd->out, "\n", 1); + indent(pd->out, pd->level); + if (path[0] != '\0' && path[strlen(path) - 1] != ']') { + pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, name, (int) name_len); + pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, ": ", 2); + } +} + +static void prettify_cb(void *userdata, const char *name, size_t name_len, + const char *path, const struct json_token *t) { + struct prettify_data *pd = (struct prettify_data *) userdata; + switch (t->type) { + case JSON_TYPE_OBJECT_START: + case JSON_TYPE_ARRAY_START: + print_key(pd, path, name, name_len); + pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_START ? "[" : "{", + 1); + pd->level++; + break; + case JSON_TYPE_OBJECT_END: + case JSON_TYPE_ARRAY_END: + pd->level--; + if (pd->last_token != JSON_TYPE_INVALID && + pd->last_token != JSON_TYPE_ARRAY_START && + pd->last_token != JSON_TYPE_OBJECT_START) { + pd->out->printer(pd->out, "\n", 1); + indent(pd->out, pd->level); + } + pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_END ? "]" : "}", 1); + break; + case JSON_TYPE_NUMBER: + case JSON_TYPE_NULL: + case JSON_TYPE_TRUE: + case JSON_TYPE_FALSE: + case JSON_TYPE_STRING: + print_key(pd, path, name, name_len); + if (t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, t->ptr, t->len); + if (t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); + break; + default: + break; + } + pd->last_token = t->type; +} + +int json_prettify(const char *s, int len, struct json_out *out) WEAK; +int json_prettify(const char *s, int len, struct json_out *out) { + struct prettify_data pd = {out, 0, JSON_TYPE_INVALID}; + return json_walk(s, len, prettify_cb, &pd); +} + +int json_prettify_file(const char *file_name) WEAK; +int json_prettify_file(const char *file_name) { + int res = -1; + char *s = json_fread(file_name); + FILE *fp; + if (s != NULL && (fp = fopen(file_name, "wb")) != NULL) { + struct json_out out = JSON_OUT_FILE(fp); + res = json_prettify(s, strlen(s), &out); + if (res < 0) { + /* On error, restore the old content */ + fclose(fp); + fp = fopen(file_name, "wb"); + fseek(fp, 0, SEEK_SET); + fwrite(s, 1, strlen(s), fp); + } else { + fputc('\n', fp); + } + fclose(fp); + } + free(s); + return res; +} + +struct next_data { + void *handle; // Passed handle. Changed if a next entry is found + const char *path; // Path to the iterated object/array + int path_len; // Path length - optimisation + int found; // Non-0 if found the next entry + struct json_token *key; // Object's key + struct json_token *val; // Object's value + int *idx; // Array index +}; + +static void next_set_key(struct next_data *d, const char *name, int name_len, + int is_array) { + if (is_array) { + /* Array. Set index and reset key */ + if (d->key != NULL) { + d->key->len = 0; + d->key->ptr = NULL; + } + if (d->idx != NULL) *d->idx = atoi(name); + } else { + /* Object. Set key and make index -1 */ + if (d->key != NULL) { + d->key->ptr = name; + d->key->len = name_len; + } + if (d->idx != NULL) *d->idx = -1; + } +} + +static void next_cb(void *userdata, const char *name, size_t name_len, + const char *path, const struct json_token *t) { + struct next_data *d = (struct next_data *) userdata; + const char *p = path + d->path_len; + if (d->found) return; + if (d->path_len >= (int) strlen(path)) return; + if (strncmp(d->path, path, d->path_len) != 0) return; + if (strchr(p + 1, '.') != NULL) return; /* More nested objects - skip */ + if (strchr(p + 1, '[') != NULL) return; /* Ditto for arrays */ + // {OBJECT,ARRAY}_END types do not pass name, _START does. Save key. + if (t->type == JSON_TYPE_OBJECT_START || t->type == JSON_TYPE_ARRAY_START) { + // printf("SAV %s %d %p\n", path, t->type, t->ptr); + next_set_key(d, name, name_len, p[0] == '['); + } else if (d->handle == NULL || d->handle < (void *) t->ptr) { + // printf("END %s %d %p\n", path, t->type, t->ptr); + if (t->type != JSON_TYPE_OBJECT_END && t->type != JSON_TYPE_ARRAY_END) { + next_set_key(d, name, name_len, p[0] == '['); + } + if (d->val != NULL) *d->val = *t; + d->handle = (void *) t->ptr; + d->found = 1; + } +} + +static void *json_next(const char *s, int len, void *handle, const char *path, + struct json_token *key, struct json_token *val, int *i) { + struct json_token tmpval, *v = val == NULL ? &tmpval : val; + struct json_token tmpkey, *k = key == NULL ? &tmpkey : key; + int tmpidx, *pidx = i == NULL ? &tmpidx : i; + struct next_data data = {handle, path, strlen(path), 0, k, v, pidx}; + json_walk(s, len, next_cb, &data); + return data.found ? data.handle : NULL; +} + +void *json_next_key(const char *s, int len, void *handle, const char *path, + struct json_token *key, struct json_token *val) WEAK; +void *json_next_key(const char *s, int len, void *handle, const char *path, + struct json_token *key, struct json_token *val) { + return json_next(s, len, handle, path, key, val, NULL); +} + +void *json_next_elem(const char *s, int len, void *handle, const char *path, + int *idx, struct json_token *val) WEAK; +void *json_next_elem(const char *s, int len, void *handle, const char *path, + int *idx, struct json_token *val) { + return json_next(s, len, handle, path, NULL, val, idx); +} + +static int json_sprinter(struct json_out *out, const char *str, size_t len) { + size_t old_len = out->u.buf.buf == NULL ? 0 : strlen(out->u.buf.buf); + size_t new_len = len + old_len; + char *p = (char *) realloc(out->u.buf.buf, new_len + 1); + if (p != NULL) { + memcpy(p + old_len, str, len); + p[new_len] = '\0'; + out->u.buf.buf = p; + } + return len; +} + +char *json_vasprintf(const char *fmt, va_list ap) WEAK; +char *json_vasprintf(const char *fmt, va_list ap) { + struct json_out out; + memset(&out, 0, sizeof(out)); + out.printer = json_sprinter; + json_vprintf(&out, fmt, ap); + return out.u.buf.buf; +} + +char *json_asprintf(const char *fmt, ...) WEAK; +char *json_asprintf(const char *fmt, ...) { + char *result = NULL; + va_list ap; + va_start(ap, fmt); + result = json_vasprintf(fmt, ap); + va_end(ap); + return result; +} diff --git a/src/frozen/frozen.h b/src/frozen/frozen.h index 6822d33..a46b576 100644 --- a/src/frozen/frozen.h +++ b/src/frozen/frozen.h @@ -1,20 +1,19 @@ /* * Copyright (c) 2004-2013 Sergey Lyubka - * Copyright (c) 2013 Cesanta Software Limited + * Copyright (c) 2018 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 . + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Alternatively, you can license this library under a commercial - * license, as set out in . + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #ifndef CS_FROZEN_FROZEN_H_ @@ -28,6 +27,13 @@ extern "C" { #include #include +#if defined(_WIN32) && _MSC_VER < 1700 +typedef int bool; +enum { false = 0, true = 1 }; +#else +#include +#endif + /* JSON token type */ enum json_token_type { JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ @@ -94,6 +100,7 @@ typedef void (*json_walk_callback_t)(void *callback_data, const char *name, /* * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; * see `json_walk_callback_t`. + * Return number of processed bytes, or a negative error code. */ int json_walk(const char *json_string, int json_string_length, json_walk_callback_t callback, void *callback_data); @@ -127,7 +134,7 @@ extern int json_printer_file(struct json_out *, const char *, size_t); #define JSON_OUT_FILE(fp) \ { \ json_printer_file, { \ - { (void *) fp, 0, 0 } \ + { (char *) fp, 0, 0 } \ } \ } @@ -144,13 +151,34 @@ typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap); * - `%M` invokes a json_printf_callback_t function. That callback function * can consume more parameters. * - * Return number of bytes printed. If the return value is bigger then the + * Return number of bytes printed. If the return value is bigger than the * supplied buffer, that is an indicator of overflow. In the overflow case, * overflown bytes are not printed. */ int json_printf(struct json_out *, const char *fmt, ...); int json_vprintf(struct json_out *, const char *fmt, va_list ap); +/* + * Same as json_printf, but prints to a file. + * File is created if does not exist. File is truncated if already exists. + */ +int json_fprintf(const char *file_name, const char *fmt, ...); +int json_vfprintf(const char *file_name, const char *fmt, va_list ap); + +/* + * Print JSON into an allocated 0-terminated string. + * Return allocated string, or NULL on error. + * Example: + * + * ```c + * char *str = json_asprintf("{a:%H}", 3, "abc"); + * printf("%s\n", str); // Prints "616263" + * free(str); + * ``` + */ +char *json_asprintf(const char *fmt, ...); +char *json_vasprintf(const char *fmt, va_list ap); + /* * Helper %M callback that prints contiguous C arrays. * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt @@ -165,7 +193,8 @@ int json_printf_array(struct json_out *, va_list *ap); * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" * 2. Order of keys in an object is irrelevant. * 3. Several extra format specifiers are supported: - * - %B: consumes `int *`, expects boolean `true` or `false`. + * - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`), + * expects boolean `true` or `false`. * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned * string is malloc-ed, caller must free() the string. * - %V: consumes `char **`, `int *`. Expects base64-encoded string. @@ -193,7 +222,7 @@ typedef void (*json_scanner_t)(const char *str, int len, void *user_data); /* * Helper function to scan array item with given path and index. * Fills `token` with the matched JSON token. - * Return 0 if no array element found, otherwise non-0. + * Return -1 if no array element found, otherwise non-negative token length. */ int json_scanf_array_elem(const char *s, int len, const char *path, int index, struct json_token *token); @@ -207,6 +236,76 @@ int json_scanf_array_elem(const char *s, int len, const char *path, int index, */ int json_unescape(const char *src, int slen, char *dst, int dlen); +/* + * Escape a string `str`, `str_len` into the printer `out`. + * Return the number of bytes printed. + */ +int json_escape(struct json_out *out, const char *str, size_t str_len); + +/* + * Read the whole file in memory. + * Return malloc-ed file content, or NULL on error. The caller must free(). + */ +char *json_fread(const char *file_name); + +/* + * Update given JSON string `s,len` by changing the value at given `json_path`. + * The result is saved to `out`. If `json_fmt` == NULL, that deletes the key. + * If path is not present, missing keys are added. Array path without an + * index pushes a value to the end of an array. + * Return 1 if the string was changed, 0 otherwise. + * + * Example: s is a JSON string { "a": 1, "b": [ 2 ] } + * json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] } + * json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 } + * json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] } + * json_setf(s, len, out, ".b", NULL); // { "a": 1 } + */ +int json_setf(const char *s, int len, struct json_out *out, + const char *json_path, const char *json_fmt, ...); + +int json_vsetf(const char *s, int len, struct json_out *out, + const char *json_path, const char *json_fmt, va_list ap); + +/* + * Pretty-print JSON string `s,len` into `out`. + * Return number of processed bytes in `s`. + */ +int json_prettify(const char *s, int len, struct json_out *out); + +/* + * Prettify JSON file `file_name`. + * Return number of processed bytes, or negative number of error. + * On error, file content is not modified. + */ +int json_prettify_file(const char *file_name); + +/* + * Iterate over an object at given JSON `path`. + * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL + * for `key`, or `val`, in which case they won't be populated. + * Return an opaque value suitable for the next iteration, or NULL when done. + * + * Example: + * + * ```c + * void *h = NULL; + * struct json_token key, val; + * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) { + * printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr); + * } + * ``` + */ +void *json_next_key(const char *s, int len, void *handle, const char *path, + struct json_token *key, struct json_token *val); + +/* + * Iterate over an array at given JSON `path`. + * Similar to `json_next_key`, but fills array index `idx` instead of `key`. + */ +void *json_next_elem(const char *s, int len, void *handle, const char *path, + int *idx, struct json_token *val); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/mpd_client.c b/src/mpd_client.c index ae137ef..978c9b7 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -59,8 +59,8 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) int je, int_buf; float float_buf; char *p_charbuf1, *p_charbuf2; - FILE *fp; - + struct mympd_state { int a; int b; } state = { .a = 0, .b = 0 }; + #ifdef DEBUG fprintf(stdout,"Got request: %s\n",msg.p); #endif @@ -72,24 +72,10 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) return; switch(cmd_id) { - case MPD_API_SET_MYMPD_SETTINGS: - fp = fopen(mpd.statefile,"w"); - if(fp != NULL) { - fprintf(fp,"%.*s",msg.len,msg.p); - fclose(fp); - } else { - fprintf(stderr,"Cant write state file\n"); - } - break; - case MPD_API_GET_MYMPD_SETTINGS: - fp = fopen(mpd.statefile,"r"); - if(fp != NULL) { - fgets(mpd.buf, MAX_SIZE, fp); - fclose(fp); - n=strlen(mpd.buf); - } - break; - case MPD_API_SET_MPD_SETTINGS: + case MPD_API_SET_SETTINGS: + json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b); + json_fprintf(mpd.statefile, "{ notificationWeb: %d, notificationPage: %d}", state.a, state.b); + je = json_scanf(msg.p, msg.len, "{ data: { random:%u } }", &uint_buf1); if (je == 1) mpd_run_random(mpd.conn, uint_buf1); @@ -305,7 +291,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) mpd_run_rm(mpd.conn, p_charbuf1); free(p_charbuf1); break; - case MPD_API_GET_MPD_SETTINGS: + case MPD_API_GET_SETTINGS: n = mympd_put_settings(mpd.buf); break; case MPD_API_GET_STATS: @@ -630,6 +616,14 @@ int mympd_put_settings(char *buffer) char *replaygain; int len; 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); + json_scanf(content, strlen(content), "{notificationWeb: %d, notificationPage: %d}", &state.a, &state.b); + } else { + state.a=0; + state.b=0; + } status = mpd_run_status(mpd.conn); if (!status) { @@ -644,12 +638,13 @@ int mympd_put_settings(char *buffer) replaygain=strdup(pair->value); mpd_return_pair(mpd.conn, pair); } - + len = json_printf(&out, "{type:settings, data:{" "repeat:%d, single:%d, crossfade:%d, consume:%d, random:%d, " "mixrampdb: %f, mixrampdelay: %f, mpdhost : %Q, mpdport: %d, passwort_set: %B, " - "streamport: %d, coverimage: %Q, max_elements_per_page: %d, replaygain: %Q" + "streamport: %d, coverimage: %Q, max_elements_per_page: %d, replaygain: %Q," + "notificationWeb: %d, notificationPage: %d" "}}", mpd_status_get_repeat(status), mpd_status_get_single(status), @@ -662,7 +657,9 @@ int mympd_put_settings(char *buffer) mpd.password ? "true" : "false", streamport, coverimage, MAX_ELEMENTS_PER_PAGE, - replaygain + replaygain, + state.a, + state.b ); mpd_status_free(status); diff --git a/src/mpd_client.h b/src/mpd_client.h index b95795f..4025822 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -78,10 +78,8 @@ X(MPD_API_GET_ARTISTS) \ X(MPD_API_GET_CURRENT_SONG) \ X(MPD_API_WELCOME) \ - X(MPD_API_GET_MPD_SETTINGS) \ - X(MPD_API_SET_MPD_SETTINGS) \ - X(MPD_API_SET_MYMPD_SETTINGS) \ - X(MPD_API_GET_MYMPD_SETTINGS) + X(MPD_API_GET_SETTINGS) \ + X(MPD_API_SET_SETTINGS) enum mpd_cmd_ids { MPD_CMDS(GEN_ENUM)