Fossil SCM
timeline json refactoring, fixed ordering, split tags into an Array.
Commit
d6cbe37b6b63c914167d4461a40325741f24e1f7
Parent
1ecf337404e0440…
1 file changed
+121
-51
+121
-51
| --- src/json.c | ||
| +++ src/json.c | ||
| @@ -462,10 +462,80 @@ | ||
| 462 | 462 | g.json.param.v = v; |
| 463 | 463 | g.json.param.o = cson_value_get_object(v); |
| 464 | 464 | json_gc_add("$PARAMS", v, 1); |
| 465 | 465 | } |
| 466 | 466 | |
| 467 | + | |
| 468 | +/* | |
| 469 | +** Splits zStr (which must not be NULL) into tokens separated by the | |
| 470 | +** given separator character. If doDeHttp is true then each element | |
| 471 | +** will be passed through dehttpize(), otherwise they are used | |
| 472 | +** as-is. Each new element is appended to the given target array | |
| 473 | +** object, which must not be NULL and ownership of it is not changed | |
| 474 | +** by this call. | |
| 475 | +** | |
| 476 | +** On success, returns the number of items appended to target. On | |
| 477 | +** error a NEGATIVE number is returned - its absolute value is the | |
| 478 | +** number of items inserted before the error occurred. There is a | |
| 479 | +** corner case here if we fail on the 1st element, which will cause 0 | |
| 480 | +** to be returned, which the client cannot immediately distinguish as | |
| 481 | +** success or error. | |
| 482 | +** | |
| 483 | +** Achtung: leading whitespace of elements is NOT elided. We should | |
| 484 | +** add an option to do that, but don't have one yet. | |
| 485 | +** | |
| 486 | +** Achtung: empty elements will be skipped, meaning consecutive | |
| 487 | +** empty elements are collapsed. | |
| 488 | +*/ | |
| 489 | +int json_string_split( char const * zStr, | |
| 490 | + char separator, | |
| 491 | + char doDeHttp, | |
| 492 | + cson_array * target ){ | |
| 493 | + char const * p = zStr /* current byte */; | |
| 494 | + char const * head = p /* current start-of-token */; | |
| 495 | + unsigned int len = 0 /* current token's length */; | |
| 496 | + int rc = 0 /* return code (number of added elements)*/; | |
| 497 | + assert( zStr && target ); | |
| 498 | + for( ; ; ++p){ | |
| 499 | + if( !*p || (separator == *p) ){ | |
| 500 | + if( len ){/* append head..(head+len) as next array | |
| 501 | + element. */ | |
| 502 | + cson_value * part = NULL; | |
| 503 | + char * zPart = NULL; | |
| 504 | + assert( head != p ); | |
| 505 | + zPart = (char*)malloc(len+1); | |
| 506 | + assert( (zPart != NULL) && "malloc failure" ); | |
| 507 | + memcpy(zPart, head, len); | |
| 508 | + zPart[len] = 0; | |
| 509 | + if(doDeHttp){ | |
| 510 | + dehttpize(zPart); | |
| 511 | + } | |
| 512 | + if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ | |
| 513 | + part = cson_value_new_string(zPart, strlen(zPart)); | |
| 514 | + if( 0 == cson_array_append( target, part ) ){ | |
| 515 | + ++rc; | |
| 516 | + }else{ | |
| 517 | + cson_value_free(part); | |
| 518 | + rc = rc ? -rc : 0; | |
| 519 | + break; | |
| 520 | + } | |
| 521 | + }else{ | |
| 522 | + assert(0 && "i didn't think this was possible!"); | |
| 523 | + } | |
| 524 | + free(zPart); | |
| 525 | + len = 0; | |
| 526 | + } | |
| 527 | + if( !*p ){ | |
| 528 | + break; | |
| 529 | + } | |
| 530 | + head = p+1; | |
| 531 | + continue; | |
| 532 | + } | |
| 533 | + ++len; | |
| 534 | + } | |
| 535 | + return rc; | |
| 536 | +} | |
| 467 | 537 | |
| 468 | 538 | /* |
| 469 | 539 | ** Performs some common initialization of JSON-related state. Must be |
| 470 | 540 | ** called by the json_page_top() and json_cmd_top() dispatching |
| 471 | 541 | ** functions to set up the JSON stat used by the dispatched functions. |
| @@ -519,44 +589,12 @@ | ||
| 519 | 589 | Note that translating g.argv this way is overkill but allows us to |
| 520 | 590 | avoid CLI-only special-case handling in other code, e.g. |
| 521 | 591 | json_command_arg(). |
| 522 | 592 | */ |
| 523 | 593 | if( zPath ){/* Either CGI or server mode... */ |
| 524 | - /* Translate PATH_INFO into JSON for later convenience. */ | |
| 525 | - char const * p = zPath /* current byte */; | |
| 526 | - char const * head = p /* current start-of-token */; | |
| 527 | - unsigned int len = 0 /* current token's length */; | |
| 528 | - assert( g.isHTTP && "g.isHTTP should have been set by now." ); | |
| 529 | - for( ; ; ++p){ | |
| 530 | - if( !*p || ('/' == *p) ){ | |
| 531 | - if( len ){/* append head..(head+len) as next array | |
| 532 | - element. */ | |
| 533 | - cson_value * part = NULL; | |
| 534 | - char * zPart = NULL; | |
| 535 | - assert( head != p ); | |
| 536 | - zPart = (char*)malloc(len+1); | |
| 537 | - assert( (zPart != NULL) && "malloc failure" ); | |
| 538 | - memcpy(zPart, head, len); | |
| 539 | - zPart[len] = 0; | |
| 540 | - dehttpize(zPart); | |
| 541 | - if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ | |
| 542 | - part = cson_value_new_string(zPart, strlen(zPart)); | |
| 543 | - cson_array_append( g.json.cmd.a, part ); | |
| 544 | - }else{ | |
| 545 | - assert(0 && "i didn't think this was possible!"); | |
| 546 | - } | |
| 547 | - free(zPart); | |
| 548 | - len = 0; | |
| 549 | - } | |
| 550 | - if( !*p ){ | |
| 551 | - break; | |
| 552 | - } | |
| 553 | - head = p+1; | |
| 554 | - continue; | |
| 555 | - } | |
| 556 | - ++len; | |
| 557 | - } | |
| 594 | + /* Translate PATH_INFO into JSON array for later convenience. */ | |
| 595 | + json_string_split(zPath, '/', 1, g.json.cmd.a); | |
| 558 | 596 | }else{/* assume CLI mode */ |
| 559 | 597 | int i; |
| 560 | 598 | char const * arg; |
| 561 | 599 | cson_value * part; |
| 562 | 600 | for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){ |
| @@ -1404,10 +1442,11 @@ | ||
| 1404 | 1442 | |
| 1405 | 1443 | /* |
| 1406 | 1444 | ** Create a temporary table suitable for storing timeline data. |
| 1407 | 1445 | */ |
| 1408 | 1446 | static void json_timeline_temp_table(void){ |
| 1447 | + /* Field order MUST match that from json_timeline_query_XXX()!!! */ | |
| 1409 | 1448 | static const char zSql[] = |
| 1410 | 1449 | @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( |
| 1411 | 1450 | @ rid INTEGER PRIMARY KEY, |
| 1412 | 1451 | @ uuid TEXT, |
| 1413 | 1452 | @ mtime INTEGER, |
| @@ -1440,11 +1479,11 @@ | ||
| 1440 | 1479 | @ coalesce(ecomment, comment), |
| 1441 | 1480 | @ coalesce(euser, user), |
| 1442 | 1481 | @ blob.rid IN leaf, |
| 1443 | 1482 | @ bgcolor, |
| 1444 | 1483 | @ event.type, |
| 1445 | - @ (SELECT group_concat(substr(tagname,5), ' ') FROM tag, tagxref | |
| 1484 | + @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref | |
| 1446 | 1485 | @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid |
| 1447 | 1486 | @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), |
| 1448 | 1487 | @ tagid, |
| 1449 | 1488 | @ brief |
| 1450 | 1489 | @ FROM event JOIN blob |
| @@ -1458,36 +1497,45 @@ | ||
| 1458 | 1497 | ** /json/timeline/ci |
| 1459 | 1498 | ** |
| 1460 | 1499 | ** Far from complete. |
| 1461 | 1500 | */ |
| 1462 | 1501 | static cson_value * json_timeline_ci(unsigned int depth){ |
| 1502 | + static const int defaultLimit = 10; | |
| 1463 | 1503 | cson_value * payV = NULL; |
| 1464 | 1504 | cson_object * pay = NULL; |
| 1465 | 1505 | cson_value * tmp = NULL; |
| 1466 | - int limit = json_getenv_int("n",10); | |
| 1506 | + cson_value * listV = NULL; | |
| 1507 | + cson_array * list = NULL; | |
| 1508 | + int limit = json_getenv_int("n",defaultLimit); | |
| 1509 | + int check = 0; | |
| 1467 | 1510 | Stmt q; |
| 1468 | 1511 | Blob sql = empty_blob; |
| 1469 | 1512 | if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 1470 | 1513 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 1471 | 1514 | return NULL; |
| 1472 | 1515 | } |
| 1473 | - if( limit < 0 ) limit = 10; | |
| 1516 | + payV = cson_value_new_object(); | |
| 1517 | + pay = cson_value_get_object(payV); | |
| 1518 | + if( limit < 0 ) limit = defaultLimit; | |
| 1474 | 1519 | json_timeline_temp_table(); |
| 1475 | - | |
| 1476 | 1520 | blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 1477 | 1521 | blob_append(&sql, json_timeline_query_ci(), -1 ); |
| 1478 | 1522 | blob_append(&sql, "AND event.type IN('ci') ", -1); |
| 1479 | 1523 | blob_append(&sql, "ORDER BY mtime DESC ", -1); |
| 1524 | +#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ | |
| 1525 | + g.json.resultCode = (cson_rc.AllocError==check) \ | |
| 1526 | + ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ | |
| 1527 | + goto error;\ | |
| 1528 | + } | |
| 1480 | 1529 | if(limit){ |
| 1481 | 1530 | blob_appendf(&sql,"LIMIT %d ",limit); |
| 1531 | + tmp = cson_value_new_integer(limit); | |
| 1532 | + SET("limit"); | |
| 1482 | 1533 | } |
| 1483 | 1534 | db_multi_exec(blob_buffer(&sql)); |
| 1484 | - payV = cson_value_new_object(); | |
| 1485 | - pay = cson_value_get_object(payV); | |
| 1486 | -#define SET(K) cson_object_set(pay,K,tmp) | |
| 1487 | 1535 | |
| 1488 | -#if 1 | |
| 1536 | +#if 0 | |
| 1489 | 1537 | /* only for testing! */ |
| 1490 | 1538 | tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); |
| 1491 | 1539 | SET("timelineSql"); |
| 1492 | 1540 | #endif |
| 1493 | 1541 | |
| @@ -1503,22 +1551,44 @@ | ||
| 1503 | 1551 | " eventType AS eventType," |
| 1504 | 1552 | " tags AS tags," /*FIXME: split this into |
| 1505 | 1553 | a JSON array*/ |
| 1506 | 1554 | " tagId AS tagId," |
| 1507 | 1555 | " brief AS briefText" |
| 1508 | - " FROM json_timeline",-1); | |
| 1556 | + " FROM json_timeline" | |
| 1557 | + " ORDER BY mtime DESC", | |
| 1558 | + -1); | |
| 1509 | 1559 | db_prepare(&q,blob_buffer(&sql)); |
| 1510 | 1560 | tmp = NULL; |
| 1511 | - cson_sqlite3_stmt_to_json(q.pStmt, &tmp, 1); | |
| 1512 | - db_finalize(&q); | |
| 1513 | - if(tmp){ | |
| 1514 | - cson_value * rows = cson_object_take(cson_value_get_object(tmp),"rows"); | |
| 1515 | - assert(NULL != rows); | |
| 1516 | - cson_value_free(tmp); | |
| 1517 | - tmp = rows; | |
| 1518 | - } | |
| 1561 | + listV = cson_value_new_array(); | |
| 1562 | + list = cson_value_get_array(listV); | |
| 1563 | + tmp = listV; | |
| 1519 | 1564 | SET("timeline"); |
| 1565 | + while( (SQLITE_ROW == db_step(&q) )){ | |
| 1566 | + /* convert each row into a JSON object...*/ | |
| 1567 | + cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); | |
| 1568 | + cson_object * row = cson_value_get_object(rowV); | |
| 1569 | + cson_string const * tagsStr = NULL; | |
| 1570 | + if(!row){ | |
| 1571 | + /* need a way of warning about this */ | |
| 1572 | + continue; | |
| 1573 | + } | |
| 1574 | + | |
| 1575 | + /* Split tags string field into JSON Array... */ | |
| 1576 | + cson_array_append(list, rowV); | |
| 1577 | + tagsStr = cson_value_get_string(cson_object_get(row,"tags")); | |
| 1578 | + if(tagsStr){ | |
| 1579 | + cson_value * tagsV = cson_value_new_array(); | |
| 1580 | + cson_array * tags = cson_value_get_array(tagsV); | |
| 1581 | + if( 0 < json_string_split( cson_string_cstr(tagsStr), ',', 0, tags)){ | |
| 1582 | + cson_object_set(row,"tags",tagsV) | |
| 1583 | + /*replaces/deletes old tags value, invalidating tagsStr!*/; | |
| 1584 | + }else{ | |
| 1585 | + cson_value_free(tagsV); | |
| 1586 | + } | |
| 1587 | + } | |
| 1588 | + } | |
| 1589 | + db_finalize(&q); | |
| 1520 | 1590 | #undef SET |
| 1521 | 1591 | goto ok; |
| 1522 | 1592 | error: |
| 1523 | 1593 | cson_value_free(payV); |
| 1524 | 1594 | payV = NULL; |
| 1525 | 1595 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -462,10 +462,80 @@ | |
| 462 | g.json.param.v = v; |
| 463 | g.json.param.o = cson_value_get_object(v); |
| 464 | json_gc_add("$PARAMS", v, 1); |
| 465 | } |
| 466 | |
| 467 | |
| 468 | /* |
| 469 | ** Performs some common initialization of JSON-related state. Must be |
| 470 | ** called by the json_page_top() and json_cmd_top() dispatching |
| 471 | ** functions to set up the JSON stat used by the dispatched functions. |
| @@ -519,44 +589,12 @@ | |
| 519 | Note that translating g.argv this way is overkill but allows us to |
| 520 | avoid CLI-only special-case handling in other code, e.g. |
| 521 | json_command_arg(). |
| 522 | */ |
| 523 | if( zPath ){/* Either CGI or server mode... */ |
| 524 | /* Translate PATH_INFO into JSON for later convenience. */ |
| 525 | char const * p = zPath /* current byte */; |
| 526 | char const * head = p /* current start-of-token */; |
| 527 | unsigned int len = 0 /* current token's length */; |
| 528 | assert( g.isHTTP && "g.isHTTP should have been set by now." ); |
| 529 | for( ; ; ++p){ |
| 530 | if( !*p || ('/' == *p) ){ |
| 531 | if( len ){/* append head..(head+len) as next array |
| 532 | element. */ |
| 533 | cson_value * part = NULL; |
| 534 | char * zPart = NULL; |
| 535 | assert( head != p ); |
| 536 | zPart = (char*)malloc(len+1); |
| 537 | assert( (zPart != NULL) && "malloc failure" ); |
| 538 | memcpy(zPart, head, len); |
| 539 | zPart[len] = 0; |
| 540 | dehttpize(zPart); |
| 541 | if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ |
| 542 | part = cson_value_new_string(zPart, strlen(zPart)); |
| 543 | cson_array_append( g.json.cmd.a, part ); |
| 544 | }else{ |
| 545 | assert(0 && "i didn't think this was possible!"); |
| 546 | } |
| 547 | free(zPart); |
| 548 | len = 0; |
| 549 | } |
| 550 | if( !*p ){ |
| 551 | break; |
| 552 | } |
| 553 | head = p+1; |
| 554 | continue; |
| 555 | } |
| 556 | ++len; |
| 557 | } |
| 558 | }else{/* assume CLI mode */ |
| 559 | int i; |
| 560 | char const * arg; |
| 561 | cson_value * part; |
| 562 | for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){ |
| @@ -1404,10 +1442,11 @@ | |
| 1404 | |
| 1405 | /* |
| 1406 | ** Create a temporary table suitable for storing timeline data. |
| 1407 | */ |
| 1408 | static void json_timeline_temp_table(void){ |
| 1409 | static const char zSql[] = |
| 1410 | @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( |
| 1411 | @ rid INTEGER PRIMARY KEY, |
| 1412 | @ uuid TEXT, |
| 1413 | @ mtime INTEGER, |
| @@ -1440,11 +1479,11 @@ | |
| 1440 | @ coalesce(ecomment, comment), |
| 1441 | @ coalesce(euser, user), |
| 1442 | @ blob.rid IN leaf, |
| 1443 | @ bgcolor, |
| 1444 | @ event.type, |
| 1445 | @ (SELECT group_concat(substr(tagname,5), ' ') FROM tag, tagxref |
| 1446 | @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid |
| 1447 | @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), |
| 1448 | @ tagid, |
| 1449 | @ brief |
| 1450 | @ FROM event JOIN blob |
| @@ -1458,36 +1497,45 @@ | |
| 1458 | ** /json/timeline/ci |
| 1459 | ** |
| 1460 | ** Far from complete. |
| 1461 | */ |
| 1462 | static cson_value * json_timeline_ci(unsigned int depth){ |
| 1463 | cson_value * payV = NULL; |
| 1464 | cson_object * pay = NULL; |
| 1465 | cson_value * tmp = NULL; |
| 1466 | int limit = json_getenv_int("n",10); |
| 1467 | Stmt q; |
| 1468 | Blob sql = empty_blob; |
| 1469 | if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 1470 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 1471 | return NULL; |
| 1472 | } |
| 1473 | if( limit < 0 ) limit = 10; |
| 1474 | json_timeline_temp_table(); |
| 1475 | |
| 1476 | blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 1477 | blob_append(&sql, json_timeline_query_ci(), -1 ); |
| 1478 | blob_append(&sql, "AND event.type IN('ci') ", -1); |
| 1479 | blob_append(&sql, "ORDER BY mtime DESC ", -1); |
| 1480 | if(limit){ |
| 1481 | blob_appendf(&sql,"LIMIT %d ",limit); |
| 1482 | } |
| 1483 | db_multi_exec(blob_buffer(&sql)); |
| 1484 | payV = cson_value_new_object(); |
| 1485 | pay = cson_value_get_object(payV); |
| 1486 | #define SET(K) cson_object_set(pay,K,tmp) |
| 1487 | |
| 1488 | #if 1 |
| 1489 | /* only for testing! */ |
| 1490 | tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); |
| 1491 | SET("timelineSql"); |
| 1492 | #endif |
| 1493 | |
| @@ -1503,22 +1551,44 @@ | |
| 1503 | " eventType AS eventType," |
| 1504 | " tags AS tags," /*FIXME: split this into |
| 1505 | a JSON array*/ |
| 1506 | " tagId AS tagId," |
| 1507 | " brief AS briefText" |
| 1508 | " FROM json_timeline",-1); |
| 1509 | db_prepare(&q,blob_buffer(&sql)); |
| 1510 | tmp = NULL; |
| 1511 | cson_sqlite3_stmt_to_json(q.pStmt, &tmp, 1); |
| 1512 | db_finalize(&q); |
| 1513 | if(tmp){ |
| 1514 | cson_value * rows = cson_object_take(cson_value_get_object(tmp),"rows"); |
| 1515 | assert(NULL != rows); |
| 1516 | cson_value_free(tmp); |
| 1517 | tmp = rows; |
| 1518 | } |
| 1519 | SET("timeline"); |
| 1520 | #undef SET |
| 1521 | goto ok; |
| 1522 | error: |
| 1523 | cson_value_free(payV); |
| 1524 | payV = NULL; |
| 1525 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -462,10 +462,80 @@ | |
| 462 | g.json.param.v = v; |
| 463 | g.json.param.o = cson_value_get_object(v); |
| 464 | json_gc_add("$PARAMS", v, 1); |
| 465 | } |
| 466 | |
| 467 | |
| 468 | /* |
| 469 | ** Splits zStr (which must not be NULL) into tokens separated by the |
| 470 | ** given separator character. If doDeHttp is true then each element |
| 471 | ** will be passed through dehttpize(), otherwise they are used |
| 472 | ** as-is. Each new element is appended to the given target array |
| 473 | ** object, which must not be NULL and ownership of it is not changed |
| 474 | ** by this call. |
| 475 | ** |
| 476 | ** On success, returns the number of items appended to target. On |
| 477 | ** error a NEGATIVE number is returned - its absolute value is the |
| 478 | ** number of items inserted before the error occurred. There is a |
| 479 | ** corner case here if we fail on the 1st element, which will cause 0 |
| 480 | ** to be returned, which the client cannot immediately distinguish as |
| 481 | ** success or error. |
| 482 | ** |
| 483 | ** Achtung: leading whitespace of elements is NOT elided. We should |
| 484 | ** add an option to do that, but don't have one yet. |
| 485 | ** |
| 486 | ** Achtung: empty elements will be skipped, meaning consecutive |
| 487 | ** empty elements are collapsed. |
| 488 | */ |
| 489 | int json_string_split( char const * zStr, |
| 490 | char separator, |
| 491 | char doDeHttp, |
| 492 | cson_array * target ){ |
| 493 | char const * p = zStr /* current byte */; |
| 494 | char const * head = p /* current start-of-token */; |
| 495 | unsigned int len = 0 /* current token's length */; |
| 496 | int rc = 0 /* return code (number of added elements)*/; |
| 497 | assert( zStr && target ); |
| 498 | for( ; ; ++p){ |
| 499 | if( !*p || (separator == *p) ){ |
| 500 | if( len ){/* append head..(head+len) as next array |
| 501 | element. */ |
| 502 | cson_value * part = NULL; |
| 503 | char * zPart = NULL; |
| 504 | assert( head != p ); |
| 505 | zPart = (char*)malloc(len+1); |
| 506 | assert( (zPart != NULL) && "malloc failure" ); |
| 507 | memcpy(zPart, head, len); |
| 508 | zPart[len] = 0; |
| 509 | if(doDeHttp){ |
| 510 | dehttpize(zPart); |
| 511 | } |
| 512 | if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */ |
| 513 | part = cson_value_new_string(zPart, strlen(zPart)); |
| 514 | if( 0 == cson_array_append( target, part ) ){ |
| 515 | ++rc; |
| 516 | }else{ |
| 517 | cson_value_free(part); |
| 518 | rc = rc ? -rc : 0; |
| 519 | break; |
| 520 | } |
| 521 | }else{ |
| 522 | assert(0 && "i didn't think this was possible!"); |
| 523 | } |
| 524 | free(zPart); |
| 525 | len = 0; |
| 526 | } |
| 527 | if( !*p ){ |
| 528 | break; |
| 529 | } |
| 530 | head = p+1; |
| 531 | continue; |
| 532 | } |
| 533 | ++len; |
| 534 | } |
| 535 | return rc; |
| 536 | } |
| 537 | |
| 538 | /* |
| 539 | ** Performs some common initialization of JSON-related state. Must be |
| 540 | ** called by the json_page_top() and json_cmd_top() dispatching |
| 541 | ** functions to set up the JSON stat used by the dispatched functions. |
| @@ -519,44 +589,12 @@ | |
| 589 | Note that translating g.argv this way is overkill but allows us to |
| 590 | avoid CLI-only special-case handling in other code, e.g. |
| 591 | json_command_arg(). |
| 592 | */ |
| 593 | if( zPath ){/* Either CGI or server mode... */ |
| 594 | /* Translate PATH_INFO into JSON array for later convenience. */ |
| 595 | json_string_split(zPath, '/', 1, g.json.cmd.a); |
| 596 | }else{/* assume CLI mode */ |
| 597 | int i; |
| 598 | char const * arg; |
| 599 | cson_value * part; |
| 600 | for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){ |
| @@ -1404,10 +1442,11 @@ | |
| 1442 | |
| 1443 | /* |
| 1444 | ** Create a temporary table suitable for storing timeline data. |
| 1445 | */ |
| 1446 | static void json_timeline_temp_table(void){ |
| 1447 | /* Field order MUST match that from json_timeline_query_XXX()!!! */ |
| 1448 | static const char zSql[] = |
| 1449 | @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( |
| 1450 | @ rid INTEGER PRIMARY KEY, |
| 1451 | @ uuid TEXT, |
| 1452 | @ mtime INTEGER, |
| @@ -1440,11 +1479,11 @@ | |
| 1479 | @ coalesce(ecomment, comment), |
| 1480 | @ coalesce(euser, user), |
| 1481 | @ blob.rid IN leaf, |
| 1482 | @ bgcolor, |
| 1483 | @ event.type, |
| 1484 | @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref |
| 1485 | @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid |
| 1486 | @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), |
| 1487 | @ tagid, |
| 1488 | @ brief |
| 1489 | @ FROM event JOIN blob |
| @@ -1458,36 +1497,45 @@ | |
| 1497 | ** /json/timeline/ci |
| 1498 | ** |
| 1499 | ** Far from complete. |
| 1500 | */ |
| 1501 | static cson_value * json_timeline_ci(unsigned int depth){ |
| 1502 | static const int defaultLimit = 10; |
| 1503 | cson_value * payV = NULL; |
| 1504 | cson_object * pay = NULL; |
| 1505 | cson_value * tmp = NULL; |
| 1506 | cson_value * listV = NULL; |
| 1507 | cson_array * list = NULL; |
| 1508 | int limit = json_getenv_int("n",defaultLimit); |
| 1509 | int check = 0; |
| 1510 | Stmt q; |
| 1511 | Blob sql = empty_blob; |
| 1512 | if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 1513 | g.json.resultCode = FSL_JSON_E_DENIED; |
| 1514 | return NULL; |
| 1515 | } |
| 1516 | payV = cson_value_new_object(); |
| 1517 | pay = cson_value_get_object(payV); |
| 1518 | if( limit < 0 ) limit = defaultLimit; |
| 1519 | json_timeline_temp_table(); |
| 1520 | blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 1521 | blob_append(&sql, json_timeline_query_ci(), -1 ); |
| 1522 | blob_append(&sql, "AND event.type IN('ci') ", -1); |
| 1523 | blob_append(&sql, "ORDER BY mtime DESC ", -1); |
| 1524 | #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 1525 | g.json.resultCode = (cson_rc.AllocError==check) \ |
| 1526 | ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 1527 | goto error;\ |
| 1528 | } |
| 1529 | if(limit){ |
| 1530 | blob_appendf(&sql,"LIMIT %d ",limit); |
| 1531 | tmp = cson_value_new_integer(limit); |
| 1532 | SET("limit"); |
| 1533 | } |
| 1534 | db_multi_exec(blob_buffer(&sql)); |
| 1535 | |
| 1536 | #if 0 |
| 1537 | /* only for testing! */ |
| 1538 | tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); |
| 1539 | SET("timelineSql"); |
| 1540 | #endif |
| 1541 | |
| @@ -1503,22 +1551,44 @@ | |
| 1551 | " eventType AS eventType," |
| 1552 | " tags AS tags," /*FIXME: split this into |
| 1553 | a JSON array*/ |
| 1554 | " tagId AS tagId," |
| 1555 | " brief AS briefText" |
| 1556 | " FROM json_timeline" |
| 1557 | " ORDER BY mtime DESC", |
| 1558 | -1); |
| 1559 | db_prepare(&q,blob_buffer(&sql)); |
| 1560 | tmp = NULL; |
| 1561 | listV = cson_value_new_array(); |
| 1562 | list = cson_value_get_array(listV); |
| 1563 | tmp = listV; |
| 1564 | SET("timeline"); |
| 1565 | while( (SQLITE_ROW == db_step(&q) )){ |
| 1566 | /* convert each row into a JSON object...*/ |
| 1567 | cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); |
| 1568 | cson_object * row = cson_value_get_object(rowV); |
| 1569 | cson_string const * tagsStr = NULL; |
| 1570 | if(!row){ |
| 1571 | /* need a way of warning about this */ |
| 1572 | continue; |
| 1573 | } |
| 1574 | |
| 1575 | /* Split tags string field into JSON Array... */ |
| 1576 | cson_array_append(list, rowV); |
| 1577 | tagsStr = cson_value_get_string(cson_object_get(row,"tags")); |
| 1578 | if(tagsStr){ |
| 1579 | cson_value * tagsV = cson_value_new_array(); |
| 1580 | cson_array * tags = cson_value_get_array(tagsV); |
| 1581 | if( 0 < json_string_split( cson_string_cstr(tagsStr), ',', 0, tags)){ |
| 1582 | cson_object_set(row,"tags",tagsV) |
| 1583 | /*replaces/deletes old tags value, invalidating tagsStr!*/; |
| 1584 | }else{ |
| 1585 | cson_value_free(tagsV); |
| 1586 | } |
| 1587 | } |
| 1588 | } |
| 1589 | db_finalize(&q); |
| 1590 | #undef SET |
| 1591 | goto ok; |
| 1592 | error: |
| 1593 | cson_value_free(payV); |
| 1594 | payV = NULL; |
| 1595 |