Fossil

Check-in [d6cbe37b]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:timeline json refactoring, fixed ordering, split tags into an Array.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | json
Files: files | file ages | folders
SHA1: d6cbe37b6b63c914167d4461a40325741f24e1f7
User & Date: stephan 2011-09-21 15:22:30
Context
2011-09-21
16:31
started adding infrastructure to report non-fatal warnings. check-in: ad50fe95 user: stephan tags: json
15:22
timeline json refactoring, fixed ordering, split tags into an Array. check-in: d6cbe37b user: stephan tags: json
14:42
refactored the prototype timeline code, split off completely from www version (different requirements). check-in: 1ecf3374 user: stephan tags: json
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/json.c.

460
461
462
463
464
465
466






































































467
468
469
470
471
472
473
...
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
....
1402
1403
1404
1405
1406
1407
1408

1409
1410
1411
1412
1413
1414
1415
....
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
....
1456
1457
1458
1459
1460
1461
1462

1463
1464
1465


1466

1467
1468
1469
1470
1471
1472


1473
1474
1475
1476
1477
1478
1479





1480
1481


1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
....
1501
1502
1503
1504
1505
1506
1507
1508


1509
1510
1511
1512
1513
1514
1515





















1516
1517
1518
1519



1520
1521
1522
1523
1524
1525
1526
  */
  v = cson_value_new_object();
  g.json.param.v = v;
  g.json.param.o = cson_value_get_object(v);
  json_gc_add("$PARAMS", v, 1);
}








































































/*
** Performs some common initialization of JSON-related state.  Must be
** called by the json_page_top() and json_cmd_top() dispatching
** functions to set up the JSON stat used by the dispatched functions.
**
** Implicitly sets up the login information state in CGI mode, but
................................................................................
    that we can simplify command dispatching later on.

    Note that translating g.argv this way is overkill but allows us to
    avoid CLI-only special-case handling in other code, e.g.
    json_command_arg().
  */
  if( zPath ){/* Either CGI or server mode... */
    /* Translate PATH_INFO into JSON for later convenience. */
    char const * p = zPath /* current byte */;
    char const * head = p  /* current start-of-token */;
    unsigned int len = 0   /* current token's length */;
    assert( g.isHTTP && "g.isHTTP should have been set by now." );
    for( ; ; ++p){
      if( !*p || ('/' == *p) ){
        if( len ){/* append head..(head+len) as next array
                     element. */
          cson_value * part = NULL;
          char * zPart = NULL;
          assert( head != p );
          zPart = (char*)malloc(len+1);
          assert( (zPart != NULL) && "malloc failure" );
          memcpy(zPart, head, len);
          zPart[len] = 0;
          dehttpize(zPart);
          if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */
            part = cson_value_new_string(zPart, strlen(zPart));
            cson_array_append( g.json.cmd.a, part );
          }else{
            assert(0 && "i didn't think this was possible!");
          }
          free(zPart);
          len = 0;
        }
        if( !*p ){
          break;
        }
        head = p+1;
        continue;
      }
      ++len;
    }
  }else{/* assume CLI mode */
    int i;
    char const * arg;
    cson_value * part;
    for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
      arg = g.argv[i];
      if( !arg || !*arg ){
................................................................................
  }
}

/*
** Create a temporary table suitable for storing timeline data.
*/
static void json_timeline_temp_table(void){

  static const char zSql[] = 
    @ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
    @   rid INTEGER PRIMARY KEY,
    @   uuid TEXT,
    @   mtime INTEGER,
    @   timestampString TEXT,
    @   comment TEXT,
................................................................................
    @   strftime('%%s',event.mtime),
    @   datetime(event.mtime,'utc'),
    @   coalesce(ecomment, comment),
    @   coalesce(euser, user),
    @   blob.rid IN leaf,
    @   bgcolor,
    @   event.type,
    @   (SELECT group_concat(substr(tagname,5), ' ') FROM tag, tagxref
    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0),
    @   tagid,
    @   brief
    @  FROM event JOIN blob 
    @ WHERE blob.rid=event.objid
  ;
................................................................................

/*
** /json/timeline/ci
**
** Far from complete.
*/
static cson_value * json_timeline_ci(unsigned int depth){

  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_value * tmp = NULL;


  int limit = json_getenv_int("n",10);

  Stmt q;
  Blob sql = empty_blob;
  if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){
    g.json.resultCode = FSL_JSON_E_DENIED;
    return NULL;
  }


  if( limit < 0 ) limit = 10;
  json_timeline_temp_table();

  blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1);
  blob_append(&sql, json_timeline_query_ci(), -1 );
  blob_append(&sql, "AND event.type IN('ci') ", -1);
  blob_append(&sql, "ORDER BY mtime DESC ", -1);





  if(limit){
    blob_appendf(&sql,"LIMIT %d ",limit);


  }
  db_multi_exec(blob_buffer(&sql));
  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
#define SET(K) cson_object_set(pay,K,tmp)

#if 1
  /* only for testing! */
  tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
  SET("timelineSql");
#endif

  blob_reset(&sql);
  blob_append(&sql, "SELECT rid AS rid,"
................................................................................
              " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */
              " bgColor AS bgColor,"
              " eventType AS eventType,"
              " tags AS tags," /*FIXME: split this into
                                 a JSON array*/
              " tagId AS tagId,"
              " brief AS briefText"
              " FROM json_timeline",-1);


  db_prepare(&q,blob_buffer(&sql));
  tmp = NULL;
  cson_sqlite3_stmt_to_json(q.pStmt, &tmp, 1);
  db_finalize(&q);
  if(tmp){
    cson_value * rows = cson_object_take(cson_value_get_object(tmp),"rows");
    assert(NULL != rows);





















    cson_value_free(tmp);
    tmp = rows;
  }
  SET("timeline");



#undef SET
  goto ok;
  error:
  cson_value_free(payV);
  payV = NULL;
  ok:
  return payV;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>







 







|







 







>



>
>
|
>






>
>
|

<




>
>
>
>
>


>
>


<
<
<

|







 







|
>
>


<
<
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
|
<
>
>
>







460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
...
587
588
589
590
591
592
593
594
595
































596
597
598
599
600
601
602
....
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
....
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
....
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519

1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534



1535
1536
1537
1538
1539
1540
1541
1542
1543
....
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560


1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585

1586

1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
  */
  v = cson_value_new_object();
  g.json.param.v = v;
  g.json.param.o = cson_value_get_object(v);
  json_gc_add("$PARAMS", v, 1);
}


/*
** Splits zStr (which must not be NULL) into tokens separated by the
** given separator character. If doDeHttp is true then each element
** will be passed through dehttpize(), otherwise they are used
** as-is. Each new element is appended to the given target array
** object, which must not be NULL and ownership of it is not changed
** by this call.
**
** On success, returns the number of items appended to target. On
** error a NEGATIVE number is returned - its absolute value is the
** number of items inserted before the error occurred. There is a
** corner case here if we fail on the 1st element, which will cause 0
** to be returned, which the client cannot immediately distinguish as
** success or error.
**
** Achtung: leading whitespace of elements is NOT elided. We should
** add an option to do that, but don't have one yet.
**
** Achtung: empty elements will be skipped, meaning consecutive
** empty elements are collapsed.
*/
int json_string_split( char const * zStr,
                       char separator,
                       char doDeHttp,
                       cson_array * target ){
  char const * p = zStr /* current byte */;
  char const * head = p  /* current start-of-token */;
  unsigned int len = 0   /* current token's length */;
  int rc = 0   /* return code (number of added elements)*/;
  assert( zStr && target );
  for( ; ; ++p){
    if( !*p || (separator == *p) ){
      if( len ){/* append head..(head+len) as next array
                   element. */
        cson_value * part = NULL;
        char * zPart = NULL;
        assert( head != p );
        zPart = (char*)malloc(len+1);
        assert( (zPart != NULL) && "malloc failure" );
        memcpy(zPart, head, len);
        zPart[len] = 0;
        if(doDeHttp){
          dehttpize(zPart);
        }
        if( *zPart ){ /* should only fail if someone manages to url-encoded a NUL byte */
          part = cson_value_new_string(zPart, strlen(zPart));
          if( 0 == cson_array_append( target, part ) ){
            ++rc;
          }else{
            cson_value_free(part);
            rc = rc ? -rc : 0;
            break;
          }
        }else{
          assert(0 && "i didn't think this was possible!");
        }
        free(zPart);
        len = 0;
      }
      if( !*p ){
        break;
      }
      head = p+1;
      continue;
    }
    ++len;
  }
  return rc;
}

/*
** Performs some common initialization of JSON-related state.  Must be
** called by the json_page_top() and json_cmd_top() dispatching
** functions to set up the JSON stat used by the dispatched functions.
**
** Implicitly sets up the login information state in CGI mode, but
................................................................................
    that we can simplify command dispatching later on.

    Note that translating g.argv this way is overkill but allows us to
    avoid CLI-only special-case handling in other code, e.g.
    json_command_arg().
  */
  if( zPath ){/* Either CGI or server mode... */
    /* Translate PATH_INFO into JSON array for later convenience. */
    json_string_split(zPath, '/', 1, g.json.cmd.a);
































  }else{/* assume CLI mode */
    int i;
    char const * arg;
    cson_value * part;
    for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
      arg = g.argv[i];
      if( !arg || !*arg ){
................................................................................
  }
}

/*
** Create a temporary table suitable for storing timeline data.
*/
static void json_timeline_temp_table(void){
  /* Field order MUST match that from json_timeline_query_XXX()!!! */
  static const char zSql[] = 
    @ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
    @   rid INTEGER PRIMARY KEY,
    @   uuid TEXT,
    @   mtime INTEGER,
    @   timestampString TEXT,
    @   comment TEXT,
................................................................................
    @   strftime('%%s',event.mtime),
    @   datetime(event.mtime,'utc'),
    @   coalesce(ecomment, comment),
    @   coalesce(euser, user),
    @   blob.rid IN leaf,
    @   bgcolor,
    @   event.type,
    @   (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref
    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0),
    @   tagid,
    @   brief
    @  FROM event JOIN blob 
    @ WHERE blob.rid=event.objid
  ;
................................................................................

/*
** /json/timeline/ci
**
** Far from complete.
*/
static cson_value * json_timeline_ci(unsigned int depth){
  static const int defaultLimit = 10;
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_value * tmp = NULL;
  cson_value * listV = NULL;
  cson_array * list = NULL;
  int limit = json_getenv_int("n",defaultLimit);
  int check = 0;
  Stmt q;
  Blob sql = empty_blob;
  if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){
    g.json.resultCode = FSL_JSON_E_DENIED;
    return NULL;
  }
  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  if( limit < 0 ) limit = defaultLimit;
  json_timeline_temp_table();

  blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1);
  blob_append(&sql, json_timeline_query_ci(), -1 );
  blob_append(&sql, "AND event.type IN('ci') ", -1);
  blob_append(&sql, "ORDER BY mtime DESC ", -1);
#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \
    g.json.resultCode = (cson_rc.AllocError==check) \
      ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \
    goto error;\
  }
  if(limit){
    blob_appendf(&sql,"LIMIT %d ",limit);
    tmp = cson_value_new_integer(limit);
    SET("limit");
  }
  db_multi_exec(blob_buffer(&sql));




#if 0
  /* only for testing! */
  tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)));
  SET("timelineSql");
#endif

  blob_reset(&sql);
  blob_append(&sql, "SELECT rid AS rid,"
................................................................................
              " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */
              " bgColor AS bgColor,"
              " eventType AS eventType,"
              " tags AS tags," /*FIXME: split this into
                                 a JSON array*/
              " tagId AS tagId,"
              " brief AS briefText"
              " FROM json_timeline"
              " ORDER BY mtime DESC",
              -1);
  db_prepare(&q,blob_buffer(&sql));
  tmp = NULL;


  listV = cson_value_new_array();
  list = cson_value_get_array(listV);
  tmp = listV;
  SET("timeline");
  while( (SQLITE_ROW == db_step(&q) )){
    /* convert each row into a JSON object...*/
    cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt);
    cson_object * row = cson_value_get_object(rowV);
    cson_string const * tagsStr = NULL;
    if(!row){
      /* need a way of warning about this */
      continue;
    }

    /* Split tags string field into JSON Array... */
    cson_array_append(list, rowV);
    tagsStr = cson_value_get_string(cson_object_get(row,"tags"));
    if(tagsStr){
      cson_value * tagsV = cson_value_new_array();
      cson_array * tags = cson_value_get_array(tagsV);
      if( 0 < json_string_split( cson_string_cstr(tagsStr), ',', 0, tags)){
        cson_object_set(row,"tags",tagsV)
          /*replaces/deletes old tags value, invalidating tagsStr!*/;
      }else{
        cson_value_free(tagsV);

      }

    }
  }
  db_finalize(&q);
#undef SET
  goto ok;
  error:
  cson_value_free(payV);
  payV = NULL;
  ok:
  return payV;