--- /dev/null
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "json.h"
+
+/*
+ * RFC 8259
+ * https://tools.ietf.org/html/rfc8259
+ */
+
+#define PARSER_STATES_ALLOC 32
+
+JSONParser* json_parser_new(void) {
+ JSONParser *parser = calloc(1, sizeof(JSONParser));
+ if(!parser) {
+ return NULL;
+ }
+
+ parser->states_alloc = PARSER_STATES_ALLOC;
+ parser->states = calloc(PARSER_STATES_ALLOC, sizeof(int));
+ if(!parser->states) {
+ free(parser);
+ return NULL;
+ }
+
+ parser->reader_array_alloc = 8;
+
+ return parser;
+}
+
+void json_parser_fill(JSONParser *p, const char *buf, size_t size) {
+ p->buffer = buf;
+ p->size = size;
+ p->pos = 0;
+}
+
+
+static JSONToken nulltoken = { JSON_NO_TOKEN, NULL, 0, 0 };
+
+int token_append(JSONToken *token, const char *buf, size_t len) {
+ if(len == 0) {
+ return 0;
+ }
+
+ size_t newlen = token->length + len;
+ if(token->alloc < newlen) {
+ char *newbuf = realloc(
+ token->alloc == 0 ? NULL : (char*)token->content,
+ newlen);
+ if(!newbuf) {
+ return 1;
+ }
+ token->content = newbuf;
+ token->alloc = newlen;
+ }
+
+ memcpy((char*)token->content+token->length, buf, len);
+ token->length = newlen;
+ return 0;
+}
+
+JSONToken get_content(JSONParser *p, size_t start, size_t end) {
+ JSONToken token = nulltoken;
+ size_t part2 = end - start;
+ if(p->uncompleted.tokentype == JSON_NO_TOKEN) {
+ token.content = p->buffer + start;
+ token.length = part2;
+ } else if(part2 == 0) {
+ token = p->uncompleted;
+ } else {
+ if(token_append(&p->uncompleted, p->buffer+start, end - start)) {
+ return nulltoken;
+ }
+ token = p->uncompleted;
+ }
+ p->uncompleted = nulltoken;
+ return token;
+}
+
+int token_isliteral(const char *content, size_t length) {
+ if(length == 4) {
+ if(!memcmp(content, "true", 4)) {
+ return 1;
+ } else if(!memcmp(content, "null", 4)) {
+ return 1;
+ }
+ } else if(length == 5 && !memcmp(content, "false", 5)) {
+ return 1;
+ }
+ return 0;
+}
+
+static int num_isexp(const char *content, size_t length, size_t pos) {
+ if(pos >= length) {
+ return 0;
+ }
+
+ int ok = 0;
+ for(size_t i=pos;i<length;i++) {
+ int c = content[i];
+ if(isdigit(c)) {
+ ok = 1;
+ } else if(i == pos) {
+ if(!(c == '+' || c == '-')) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ return ok;
+}
+
+JSONTokenType token_numbertype(const char *content, size_t length) {
+ if(length == 0) return JSON_TOKEN_ERROR;
+
+ if(content[0] != '-' && !isdigit(content[0])) {
+ return JSON_TOKEN_ERROR;
+ }
+
+ JSONTokenType type = JSON_TOKEN_INTEGER;
+ for(size_t i=1;i<length;i++) {
+ if(content[i] == '.') {
+ if(type == JSON_TOKEN_NUMBER) {
+ return JSON_TOKEN_ERROR; // more than one decimal separator
+ }
+ type = JSON_TOKEN_NUMBER;
+ } else if(content[i] == 'e' || content[i] == 'E') {
+ return num_isexp(content, length, i+1) ? JSON_TOKEN_NUMBER : JSON_TOKEN_ERROR;
+ } else if(!isdigit(content[i])) {
+ return JSON_TOKEN_ERROR; // char is not a diget, decimal separator or exponent sep
+ }
+ }
+
+ return type;
+}
+
+JSONToken get_token(JSONParser *p, size_t start, size_t end) {
+ JSONToken token = get_content(p, start, end);
+ if(token_isliteral(token.content, token.length)) {
+ token.tokentype = JSON_TOKEN_LITERAL;
+ } else {
+ token.tokentype = token_numbertype(token.content, token.length);
+ }
+ p->pos = end;
+ return token;
+}
+
+static JSONTokenType char2ttype(char c) {
+ switch(c) {
+ case '[': {
+ return JSON_TOKEN_BEGIN_ARRAY;
+ }
+ case '{': {
+ return JSON_TOKEN_BEGIN_OBJECT;
+ }
+ case ']': {
+ return JSON_TOKEN_END_ARRAY;
+ }
+ case '}': {
+ return JSON_TOKEN_END_OBJECT;
+ }
+ case ':': {
+ return JSON_TOKEN_NAME_SEPARATOR;
+ }
+ case ',': {
+ return JSON_TOKEN_VALUE_SEPARATOR;
+ }
+ case '"': {
+ return JSON_TOKEN_STRING;
+ }
+ default: {
+ if(isspace(c)) {
+ return JSON_TOKEN_SPACE;
+ }
+ }
+ }
+ return JSON_NO_TOKEN;
+}
+
+JSONToken json_parser_next_token(JSONParser *p) {
+ // current token type and start index
+ JSONTokenType ttype = p->uncompleted.tokentype;
+ size_t token_start = p->pos;
+
+ for(size_t i=p->pos;i<p->size;i++) {
+ char c = p->buffer[i];
+ if(ttype != JSON_TOKEN_STRING) {
+ // currently non-string token
+
+ JSONTokenType ctype = char2ttype(c); // start of new token?
+
+ if(ttype == JSON_NO_TOKEN) {
+ if(ctype == JSON_TOKEN_SPACE) {
+ continue;
+ } else if(ctype == JSON_TOKEN_STRING) {
+ // begin string
+ ttype = JSON_TOKEN_STRING;
+ token_start = i;
+ } else if(ctype != JSON_NO_TOKEN) {
+ // single-char token
+ p->pos = i + 1;
+ JSONToken token = { ctype, NULL, 0, 0};
+ return token;
+ } else {
+ ttype = JSON_TOKEN_LITERAL; // number or literal
+ token_start = i;
+ }
+ } else {
+ // finish token
+ if(ctype != JSON_NO_TOKEN) {
+ return get_token(p, token_start, i);
+ }
+ }
+ } else {
+ // currently inside a string
+ if(!p->tokenizer_escape) {
+ if(c == '"') {
+ JSONToken ret = get_content(p, token_start, i+1);
+ ret.tokentype = JSON_TOKEN_STRING;
+ p->pos = i+1;
+ return ret;
+ } else if(c == '\\') {
+ p->tokenizer_escape = 1;
+ }
+ } else {
+ p->tokenizer_escape = 0;
+ }
+ }
+ }
+
+ if(ttype != JSON_NO_TOKEN) {
+ // uncompleted token
+ size_t uncompeted_len = p->size - token_start;
+ if(p->uncompleted.tokentype == JSON_NO_TOKEN) {
+ // current token is uncompleted
+ // save current token content in p->uncompleted
+ JSONToken uncompleted;
+ uncompleted.tokentype = ttype;
+ uncompleted.length = uncompeted_len;
+ uncompleted.alloc = uncompeted_len + 16;
+ char *tmp = malloc(uncompleted.alloc);
+ if(tmp) {
+ memcpy(tmp, p->buffer+token_start, uncompeted_len);
+ uncompleted.content = tmp;
+ p->uncompleted = uncompleted;
+ } else {
+ p->error = 1;
+ }
+ } else {
+ // previously we also had an uncompleted token
+ // combine the uncompleted token with the current token
+ if(token_append(&p->uncompleted, p->buffer+token_start, uncompeted_len)) {
+ p->error = 1;
+ }
+ }
+ }
+
+ JSONToken ret = { JSON_NO_TOKEN, NULL, 0, 0};
+ return ret;
+}
+
+static int create_string(JSONToken token, JSONValue **value) {
+ JSONValue *v = malloc(sizeof(JSONValue));
+ if(!v) {
+ *value = NULL;
+ return -1;
+ }
+ v->type = JSON_STRING;
+
+ char *str = malloc(token.length+1);
+ if(!str) {
+ free(v);
+ *value = NULL;
+ return -1;
+ }
+ memcpy(str, token.content, token.length);
+ str[token.length] = 0;
+
+ v->type = JSON_STRING;
+ v->value.string.string = str;
+ v->value.string.length = token.length;
+ *value = v;
+ return 0;
+}
+
+typedef struct json_ustr {
+ char *ptr;
+ size_t length;
+} json_ustr;
+static json_ustr unescape_string(const char *str, size_t len) {
+ char *newstr = malloc(len+1);
+ if(!newstr) {
+ json_ustr r;
+ r.ptr = NULL;
+ r.length = 0;
+ return r;
+ }
+
+ int j = 0;
+ int u = 0;
+ for(int i=1;i<len-1;i++) {
+ char c = str[i];
+ if(!u) {
+ if(c == '\\') {
+ u = 1;
+ } else {
+ newstr[j++] = c;
+ }
+ } else {
+ u = 0;
+ if(c == 'n') {
+ c = '\n';
+ } else if(c == 't') {
+ c = '\t';
+ }
+ newstr[j++] = c;
+ }
+ }
+ newstr[j] = 0;
+
+ json_ustr r;
+ r.ptr = newstr;
+ r.length = j;
+ return r;
+}
+
+static int parse_integer(const char *str, size_t len, int64_t *value) {
+ char *endptr = NULL;
+ char buf[32];
+ if(len > 30) {
+ return 1;
+ }
+ memcpy(buf, str, len);
+ buf[len] = 0;
+
+ long long v = strtoll(buf, &endptr, 10);
+ if(endptr != &buf[len]) {
+ return 1;
+ }
+ *value = (int64_t)v;
+
+ return 0;
+}
+
+static int parse_number(const char *str, size_t len, double *value) {
+ char *endptr = NULL;
+ char buf[32];
+ if(len > 30) {
+ return 1;
+ }
+ memcpy(buf, str, len);
+ buf[len] = 0;
+
+ double v = strtod(buf, &endptr);
+ if(endptr != &buf[len]) {
+ return 1;
+ }
+ *value = v;
+
+ return 0;
+}
+
+static int add_state(JSONParser *p, int state) {
+ if(p->nstates >= p->states_alloc) {
+ p->states_alloc += PARSER_STATES_ALLOC;
+ p->states = realloc(p->states, p->states_alloc * sizeof(int));
+ if(!p->states) {
+ return 1;
+ }
+ }
+ p->states[++p->nstates] = state;
+ return 0;
+}
+
+static void end_elm(JSONParser *p, JSONReaderType type) {
+ p->reader_type = type;
+ p->nstates--;
+}
+
+#define JP_STATE_VALUE_BEGIN 0
+#define JP_STATE_VALUE_BEGIN_OBJ 1
+#define JP_STATE_VALUE_BEGIN_AR 2
+#define JP_STATE_ARRAY_SEP_OR_CLOSE 3
+#define JP_STATE_OBJ_NAME_OR_CLOSE 4
+#define JP_STATE_OBJ_NAME 5
+#define JP_STATE_OBJ_COLON 6
+#define JP_STATE_OBJ_SEP_OR_CLOSE 7
+
+static int next_state_after_value(int current) {
+ switch(current) {
+ default: return -1;
+ // after value JSON complete, expect nothing
+ case JP_STATE_VALUE_BEGIN: return -1;
+ // after obj value, expect ',' or '}'
+ case JP_STATE_VALUE_BEGIN_OBJ: return JP_STATE_OBJ_SEP_OR_CLOSE;
+ // after array value, expect ',' or ']'
+ case JP_STATE_VALUE_BEGIN_AR: return JP_STATE_ARRAY_SEP_OR_CLOSE;
+ }
+}
+
+static void clear_valuename(JSONParser *p) {
+ if(p->value_name) free(p->value_name);
+ p->value_name = NULL;
+ p->value_name_len = 0;
+}
+
+static void clear_values(JSONParser *p) {
+ if(p->value_str) free(p->value_str);
+ p->value_str = NULL;
+ p->value_str_len = 0;
+ p->value_int = 0;
+ p->value_double = 0;
+}
+
+int json_read(JSONParser *p) {
+ int state = p->states[p->nstates];
+ clear_values(p);
+ JSONToken token = json_parser_next_token(p);
+ p->reader_token = token;
+
+
+ p->value_ready = 0;
+
+ if(token.tokentype == JSON_NO_TOKEN) {
+ return 0;
+ }
+
+ int ret = 1;
+
+ // 0 JP_STATE_VALUE_BEGIN value begin
+ // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object)
+ // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array)
+ // 3 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose
+ // 4 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose
+ // 5 JP_STATE_OBJ_NAME object, expect name
+ // 6 JP_STATE_OBJ_COLON object, expect ':'
+ // 7 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose
+
+ if(state == JP_STATE_VALUE_BEGIN_AR || state == JP_STATE_OBJ_SEP_OR_CLOSE) {
+ clear_valuename(p);
+ }
+
+ if(state < 3) {
+ // expect value
+ p->states[p->nstates] = next_state_after_value(state);
+ p->value_ready = 1;
+ switch(token.tokentype) {
+ case JSON_TOKEN_BEGIN_ARRAY: {
+ p->reader_type = JSON_READER_ARRAY_BEGIN;
+ if(add_state(p, JP_STATE_VALUE_BEGIN_AR)) return -1;
+ return 1;
+ //return json_read(p);
+ }
+ case JSON_TOKEN_BEGIN_OBJECT: {
+ p->reader_type = JSON_READER_OBJECT_BEGIN;
+ if(add_state(p, JP_STATE_OBJ_NAME_OR_CLOSE)) return -1;
+ return 1;
+ //return json_read(p);
+ }
+ case JSON_TOKEN_END_ARRAY: {
+ p->value_ready = 0;
+ end_elm(p, JSON_READER_ARRAY_END);
+ break;
+ }
+ case JSON_TOKEN_END_OBJECT: {
+ p->value_ready = 0;
+ end_elm(p, JSON_READER_OBJECT_END);
+ break;
+ }
+ case JSON_TOKEN_STRING: {
+ p->reader_type = JSON_READER_STRING;
+ json_ustr str = unescape_string(token.content, token.length);
+ if(str.ptr) {
+ p->value_str = str.ptr;
+ p->value_str_len = str.length;
+ } else {
+ return -1;
+ }
+ break;
+ }
+ case JSON_TOKEN_INTEGER: {
+ p->reader_type = JSON_READER_INTEGER;
+ int64_t value;
+ if(parse_integer(token.content, token.length, &value)) {
+ return -1;
+ }
+ p->value_int = value;
+ p->value_double = (double)value;
+ break;
+ }
+ case JSON_TOKEN_NUMBER: {
+ p->reader_type = JSON_READER_NUMBER;
+ double value;
+ if(parse_number(token.content, token.length, &value)) {
+ return -1;
+ }
+ p->value_double = value;
+ p->value_int = (int64_t)value;
+ break;
+ }
+ case JSON_TOKEN_LITERAL: {
+ p->reader_type = JSON_READER_LITERAL;
+ break;
+ }
+ default: return -1;
+ }
+ } else if(state == JP_STATE_ARRAY_SEP_OR_CLOSE) {
+ // expect ',' or ']'
+ if(token.tokentype == JSON_TOKEN_VALUE_SEPARATOR) {
+ p->states[p->nstates] = JP_STATE_VALUE_BEGIN_AR;
+ return json_read(p);
+ } else if(token.tokentype == JSON_TOKEN_END_ARRAY) {
+ end_elm(p, JSON_READER_ARRAY_END);
+ } else {
+ return -1;
+ }
+ } else if(state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) {
+ if(state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == JSON_TOKEN_END_OBJECT) {
+ clear_valuename(p);
+ end_elm(p, JSON_READER_OBJECT_END);
+ } else {
+ // expect string
+ if(token.tokentype != JSON_TOKEN_STRING) return -1;
+
+ if(p->value_name) free(p->value_name);
+ json_ustr valname = unescape_string(token.content, token.length);
+ p->value_name = valname.ptr;
+ p->value_name_len = valname.length;
+
+ // next state
+ p->states[p->nstates] = JP_STATE_OBJ_COLON;
+ return json_read(p);
+ }
+ } else if(state == JP_STATE_OBJ_COLON) {
+ // expect ':'
+ if(token.tokentype != JSON_TOKEN_NAME_SEPARATOR) return -1;
+ // next state
+ p->states[p->nstates] = 1;
+ return json_read(p);
+ } else if(state == 7) {
+ // expect ',' or '}]'
+ if(token.tokentype == JSON_TOKEN_VALUE_SEPARATOR) {
+ p->states[p->nstates] = JP_STATE_OBJ_NAME;
+ return json_read(p);
+ } else if(token.tokentype == JSON_TOKEN_END_OBJECT) {
+ end_elm(p, JSON_READER_OBJECT_END);
+ } else {
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+
+JSONReaderType json_reader_type(JSONParser *p) {
+ return p->reader_type;
+}
+
+const char* json_reader_name(JSONParser *p, size_t *opt_len) {
+ if(opt_len) *opt_len = p->value_name_len;
+ return p->value_name;
+}
+
+const char* json_reader_string(JSONParser *p, size_t *opt_len) {
+ if(opt_len) *opt_len = p->value_str_len;
+
+ if(p->reader_token.tokentype != JSON_TOKEN_STRING) {
+ return NULL;
+ }
+
+ return p->value_str;
+}
+
+int64_t json_reader_int(JSONParser *p) {
+ return p->value_int;
+}
+
+double json_reader_double(JSONParser *p) {
+ return p->value_double;
+}
+
+int json_reader_isnull(JSONParser *p) {
+ if(p->reader_token.tokentype == JSON_TOKEN_LITERAL && p->reader_token.length == 4) {
+ return !memcmp(p->reader_token.content, "null", 4);
+ }
+ return 0;
+}
+
+JSONLiteralType json_reader_literal(JSONParser *p) {
+ const char *l = p->reader_token.content;
+ if(!strcmp(l, "true")) {
+ return JSON_TRUE;
+ } else if(!strcmp(l, "false")) {
+ return JSON_FALSE;
+ }
+ return JSON_NULL;
+}
+
+int json_reader_bool(JSONParser *p) {
+ JSONLiteralType lt = json_reader_literal(p);
+ return lt == JSON_TRUE ? 1 : 0;
+}
+
+
+/* -------------------- read value functions -------------------- */
+
+static JSONValue* init_value(JSONParser *p) {
+ JSONValue *value = malloc(sizeof(JSONValue));
+ if(!value) {
+ return NULL;
+ }
+ memset(value, 0, sizeof(JSONValue));
+ return value;
+}
+
+static int setup_read_value(JSONParser *p) {
+ p->readvalue_alloc = PARSER_STATES_ALLOC;
+ p->readvalue_nelm = 0;
+ p->readvalue_stack = calloc(PARSER_STATES_ALLOC, sizeof(JSONValue*));
+ if(!p->readvalue_stack) return -1;
+
+ p->read_value = NULL;
+ p->readvalue_stack[0] = NULL;
+
+ return 0;
+}
+
+static int obj_init_values(JSONParser *p, JSONValue *v) {
+ v->value.object.values = calloc(sizeof(JSONObjValue), p->reader_array_alloc);
+ if(!v->value.object.values) {
+ return -1;
+ }
+ v->value.object.alloc = p->reader_array_alloc;
+ v->value.object.size = 0;
+
+ return 0;
+}
+
+static int obj_add_value(JSONParser *p, JSONValue *parent, JSONObjValue v) {
+ if(!parent->value.object.values) {
+ if(obj_init_values(p, parent)) {
+ return -1;
+ }
+ }
+
+ if(parent->value.object.size == parent->value.object.alloc) {
+ parent->value.object.alloc *= 2;
+ parent->value.object.values = realloc(parent->value.object.values, sizeof(JSONObjValue) * parent->value.object.alloc);
+ if(!parent->value.object.values) {
+ return -1;
+ }
+ }
+
+ parent->value.object.values[parent->value.object.size++] = v;
+
+ return 0;
+}
+
+static int array_init(JSONParser *p, JSONValue *v) {
+ v->value.array.array = calloc(sizeof(JSONValue*), p->reader_array_alloc);
+ if(!v->value.array.array) {
+ return -1;
+ }
+ v->value.array.alloc = p->reader_array_alloc;
+ v->value.array.size = 0;
+
+ return 0;
+}
+
+static int array_add_value(JSONParser *p, JSONValue *parent, JSONValue *v) {
+ if(!parent->value.array.array) {
+ if(array_init(p, parent)) {
+ return -1;
+ }
+ }
+
+ if(parent->value.array.size == parent->value.array.alloc) {
+ parent->value.array.alloc *= 2;
+ parent->value.array.array = realloc(parent->value.array.array, sizeof(JSONValue*) * parent->value.array.alloc);
+ if(!parent->value.array.array) {
+ return -1;
+ }
+ }
+
+ parent->value.array.array[parent->value.array.size++] = v;
+
+ return 0;
+}
+
+static int add_to_parent(JSONParser *p, JSONValue *parent, JSONValue *v) {
+ if(!parent) {
+ return -1; // shouldn't happen but who knows
+ }
+
+ int ret = 0;
+ if(parent->type == JSON_OBJECT) {
+ if(!p->value_name || p->value_name_len == 0) {
+ return -1;
+ }
+ char *valuename = p->value_name;
+ p->value_name = NULL;
+
+ JSONObjValue newvalue;
+ newvalue.name = valuename;
+ newvalue.value = v;
+
+ ret = obj_add_value(p, parent, newvalue);
+ } else if(parent->type == JSON_ARRAY) {
+ ret = array_add_value(p, parent, v);
+ } else {
+ ret = -1; // should also never happen
+ }
+
+ return ret;
+}
+
+
+static int readvaluestack_add(JSONParser *p, JSONValue *v) {
+ if(p->readvalue_nelm == p->readvalue_alloc) {
+ p->readvalue_alloc *= 2;
+ p->readvalue_stack = realloc(p->readvalue_stack, sizeof(JSONValue*) * p->readvalue_alloc);
+ if(!p->readvalue_stack) {
+ return -1;
+ }
+ }
+ p->readvalue_stack[p->readvalue_nelm++] = v;
+ return 0;
+}
+
+int json_read_value(JSONParser *p, JSONValue **value) {
+ *value = NULL;
+ if(!p->readvalue_stack) {
+ if(setup_read_value(p)) return -1;
+ }
+
+ while(p->readvalue_nelm > 0 || !p->read_value) {
+ //JSONValue *s = p->readvalue_stack[p->readvalue_nelm];
+ if(p->value_ready) {
+ // value available without another read
+ JSONValue *v = init_value(p);
+ if(!v) return -1;
+
+ if(p->readvalue_nelm > 0) {
+ if(add_to_parent(p, p->readvalue_stack[p->readvalue_nelm-1], v)) {
+ return -1;
+ }
+ } else {
+ // set this value as root
+ p->read_value = v;
+ }
+
+ switch(p->reader_type) {
+ case JSON_READER_OBJECT_BEGIN: {
+ v->type = JSON_OBJECT;
+ if(readvaluestack_add(p, v)) {
+ return -1;
+ }
+ break;
+ }
+ case JSON_READER_OBJECT_END: return -1; // should not happen
+ case JSON_READER_ARRAY_BEGIN: {
+ v->type = JSON_ARRAY;
+ if(readvaluestack_add(p, v)) {
+ return -1;
+ }
+ break;
+ }
+ case JSON_READER_ARRAY_END: return -1; // should not happen
+ case JSON_READER_STRING: {
+ v->type = JSON_STRING;
+ if(p->value_str) {
+ v->value.string.string = p->value_str;
+ v->value.string.length = p->value_str_len;
+ p->value_str = NULL;
+ }
+ break;
+ }
+ case JSON_READER_INTEGER: {
+ v->type = JSON_INTEGER;
+ v->value.integer.value = json_reader_int(p);
+ break;
+ }
+ case JSON_READER_NUMBER: {
+ v->type = JSON_NUMBER;
+ v->value.number.value = json_reader_double(p);
+ break;
+ }
+ case JSON_READER_LITERAL: {
+ v->type = JSON_LITERAL;
+ v->value.literal.literal = json_reader_literal(p);
+ break;
+ }
+ }
+ } else if(p->readvalue_initialized) {
+ JSONReaderType rt = p->reader_type;
+ if(rt == JSON_READER_OBJECT_END || rt == JSON_READER_ARRAY_END) {
+ p->readvalue_nelm--;
+ }
+ // else: p->value_ready is 1, this will be handled in the next run
+ }
+
+ if(p->readvalue_nelm > 0 || !p->read_value) {
+ int r = json_read(p);
+ if(r != 1) {
+ p->readvalue_initialized = 0;
+ return r;
+ }
+ p->readvalue_initialized = 1;
+ }
+ }
+
+ *value = p->read_value;
+ p->readvalue_initialized = 0;
+ p->read_value = NULL;
+
+ return 1;
+}
+
#include <spawn.h>
#include <sys/wait.h>
#include <signal.h>
-
+#include <poll.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <pthread.h>
+
+
extern char **environ;
+#define STR_BUFSIZE 512
#define WID_ARG_BUFSIZE 24
+#define PLAYER_POLL_TIMEOUT 500
+#define PLAYER_IN_BUFSIZE 8192
+
static void* start_player(void *data);
-static void* player_io_thread(void *data);
+
+static void player_io(Player *p);
void PlayerOpenFile(MainWindow *win) {
pthread_t tid;
}
}
-static void* start_player(void *data) {
- MainWindow *win = data;
+static int prepare_player(Player *player, char *log_arg, char *ipc_arg) {
+ // create tmp directory for IPC
+ char *player_tmp = NULL;
+ char buf[STR_BUFSIZE];
+ snprintf(buf, STR_BUFSIZE, "/tmp/uwplayer-%x", rand());
+ int mkdir_success = 0;
+ for(int t=0;t<5;t++) {
+ if(!mkdir(buf, S_IRWXU)) {
+ mkdir_success = 1;
+ break;
+ } else if(errno != EEXIST) {
+ break;
+ }
+ }
+ if(!mkdir_success) return 1;
+ player_tmp = strdup(buf);
+ player->tmp = player_tmp;
+
+ // prepare log/ipc args and create log fifo
+ int err = 0;
+
+ if(snprintf(log_arg, STR_BUFSIZE, "--log-file=%s/%s", player_tmp, "log.fifo") >= STR_BUFSIZE) {
+ err = 1;
+ }
+ if(snprintf(ipc_arg, STR_BUFSIZE, "--input-ipc-server=%s/%s", player_tmp, "ipc.socket") >= STR_BUFSIZE) {
+ err = 1;
+ }
+
+ snprintf(buf, STR_BUFSIZE, "%s/log.fifo", player_tmp);
+ if(err || mkfifo(buf, S_IRUSR|S_IWUSR)) {
+ rmdir(player_tmp);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int start_player_process(Player *player, MainWindow *win) {
+ char log_arg[STR_BUFSIZE];
+ char ipc_arg[STR_BUFSIZE];
+
+ if(prepare_player(player, log_arg, ipc_arg)) {
+ return 1;
+ }
char *player_bin = "/usr/local/bin/mpv"; // TODO: get bin from settings
Window wid = XtWindow(win->player_widget);
char wid_arg[WID_ARG_BUFSIZE];
if(snprintf(wid_arg, WID_ARG_BUFSIZE, "%lu", wid) >= WID_ARG_BUFSIZE) {
- return NULL;
+ return 1;
}
// create player arg list
args[0] = player_bin;
args[1] = "-wid";
args[2] = wid_arg;
- args[3] = win->file;
- args[4] = NULL;
-
- // redirect stdin/stdout
- int pout[2];
- int pin[2];
- if(pipe(pout)) {
- perror("pipe");
- return NULL;
- }
- if(pipe(pin)) {
- perror("pipe");
- return NULL;
- }
+ args[3] = "--no-terminal";
+ args[4] = log_arg;
+ args[5] = ipc_arg;
+ args[6] = win->file;
+ args[7] = NULL;
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
- posix_spawn_file_actions_adddup2(&actions, pin[0], STDIN_FILENO);
- posix_spawn_file_actions_adddup2(&actions, pout[1], STDOUT_FILENO);
+ //posix_spawn_file_actions_adddup2(&actions, pin[0], STDIN_FILENO);
+ //posix_spawn_file_actions_adddup2(&actions, pout[1], STDOUT_FILENO);
// start player
pid_t player_pid;
if(posix_spawn(&player_pid, player_bin, &actions, NULL, args, environ)) {
perror("posix_spawn");
- return NULL;
+ return 1;
}
posix_spawn_file_actions_destroy(&actions);
+ player->process = player_pid;
+ player->isactive = TRUE;
+
+ return 0;
+}
+
+static int wait_for_ipc(Player *player) {
+ char buf[STR_BUFSIZE];
+ snprintf(buf, STR_BUFSIZE, "%s/log.fifo", player->tmp); // cannot fail
+
+ // open log
+ int fd_log = open(buf, O_RDONLY);
+ if(fd_log < 0) {
+ perror("Cannot open log");
+ return 1;
+ }
+ player->log = fd_log;
+
+ // read log until IPC socket is created
+ char *scan_str = "Listening to IPC";
+ int scan_pos = 0;
+ int scan_len = strlen(scan_str);
+ int ipc_listen = 0;
+ ssize_t r;
+ while((r = read(fd_log, buf, STR_BUFSIZE)) > 0) {
+ for(int i=0;i<r;i++) {
+ char c = buf[i];
+ char *s_str = buf+i;
+
+ if(scan_pos == scan_len) {
+ ipc_listen = 1;
+ break;
+ }
+
+ if(scan_str[scan_pos] == c) {
+ scan_pos++;
+ } else {
+ scan_pos = 0;
+ }
+ }
+ if(ipc_listen) break;
+ }
+
+ return 0;
+}
+
+static int connect_to_ipc(Player *player) {
+ // connect to IPC socket
+ int fd_ipc = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(fd_ipc < 0) {
+ perror("Cannot create IPC socket");
+ return 1;
+ }
+ player->ipc = fd_ipc;
+
+ char buf[STR_BUFSIZE];
+ snprintf(buf, STR_BUFSIZE, "%s/%s", player->tmp, "ipc.socket"); // cannot fail
+
+ struct sockaddr_un ipc_addr;
+ memset(&ipc_addr, 0, sizeof(struct sockaddr_un));
+ ipc_addr.sun_family = AF_UNIX;
+ memcpy(ipc_addr.sun_path, buf, strlen(buf));
+ if(connect(fd_ipc, (struct sockaddr *)&ipc_addr, sizeof(ipc_addr)) == -1) {
+ perror("Cannot connect to IPC socket");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void* start_player(void *data) {
+ MainWindow *win = data;
+
Player *player = malloc(sizeof(Player));
memset(player, 0, sizeof(Player));
- player->in = pin[1];
- player->out = pout[0];
- close(pin[0]);
- close(pout[1]);
- player->process = player_pid;
- player->isactive = TRUE;
+ // start mpv
+ if(start_player_process(player, win)) {
+ PlayerDestroy(player);
+ return NULL;
+ }
+
+ // wait until IPC socket is ready
+ if(wait_for_ipc(player)) {
+ PlayerDestroy(player);
+ return NULL;
+ }
+
+ if(connect_to_ipc(player)) {
+ PlayerDestroy(player);
+ return NULL;
+ }
+ // set player in main window
if(win->player) {
PlayerDestroy(win->player);
}
win->player = player;
- // start thread for mplayer IO
- pthread_t tid;
- if(pthread_create(&tid, NULL, player_io_thread, player)) {
- perror("pthread_create");
- }
+ // IO
+ player_io(player);
return NULL;
}
+static void player_io(Player *p) {
+ int flags = fcntl(p->log, F_GETFL, 0);
+ fcntl(p->log, F_SETFL, flags | O_NONBLOCK);
+
+ struct pollfd fds[2];
+ fds[0].fd = p->log;
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+ fds[1].fd = p->ipc;
+ fds[1].events = POLLIN;
+ fds[1].revents = 0;
+
+ char buf[PLAYER_IN_BUFSIZE];
+ while(poll(fds, 2, PLAYER_POLL_TIMEOUT)) {
+ if(fds[0].revents == POLLIN) {
+ // clean up fifo
+ read(fds[0].fd, buf, PLAYER_IN_BUFSIZE);
+ }
+
+ if(fds[1].revents == POLLIN) {
+ ssize_t r;
+ if((r = read(fds[1].fd, buf, PLAYER_IN_BUFSIZE)) <= 0) {
+ break;
+ }
+
+ write(1, buf, r);
+
+ }
+
+ char *cmd = "{ \"command\": [\"get_property\", \"playback-time\"] }\n";
+ write(p->ipc, cmd, strlen(cmd));
+ }
+}
+
void PlayerDestroy(Player *p) {
+ if(p->log >= 0) {
+ close(p->log);
+ }
+ if(p->ipc >= 0) {
+ close(p->ipc);
+ }
+
+ if(p->tmp) {
+ free(p->tmp);
+ }
+
if(p->isactive) {
- close(p->in);
- close(p->out);
kill(p->process, SIGTERM);
}
+
free(p);
}
-static void* player_io_thread(void *data) {
- Player *player = data;
-
- return NULL;
-}