/* ** Very incomplete/incorrect impl of /json/artifact/TICKET_ID. */ cson_value * json_artifact_ticket( cson_object * zParent, int rid ){ cson_object * pay = NULL; Manifest *pTktChng = NULL; static cson_value * eventTypeLabel = NULL; if(! g.perm.RdTkt ){ g.json.resultCode = FSL_JSON_E_DENIED; return NULL; } if(!eventTypeLabel){ eventTypeLabel = json_new_string("ticket"); json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel); } pTktChng = manifest_get(rid, CFTYPE_TICKET); if( pTktChng==0 ){ g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED; return NULL; } pay = cson_new_object(); cson_object_set(pay, "eventType", eventTypeLabel ); cson_object_set(pay, "uuid", json_new_string(pTktChng->zTicketUuid)); cson_object_set(pay, "user", json_new_string(pTktChng->zUser)); cson_object_set(pay, "timestamp", json_julian_to_timestamp(pTktChng->rDate)); manifest_destroy(pTktChng); return cson_object_value(pay); }
/* ** Implementation of the /json/diff page. ** ** Arguments: ** ** v1=1st version to diff ** v2=2nd version to diff ** ** Can come from GET, POST.payload, CLI -v1/-v2 or as positional ** parameters following the command name (in HTTP and CLI modes). ** */ cson_value * json_page_diff(){ cson_object * pay = NULL; cson_value * v = NULL; char const * zFrom; char const * zTo; int nContext = 0; char doSBS; char doHtml; if(!g.perm.Read){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } zFrom = json_find_option_cstr("v1",NULL,NULL); if(!zFrom){ zFrom = json_command_arg(2); } if(!zFrom){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Required 'v1' parameter is missing."); return NULL; } zTo = json_find_option_cstr("v2",NULL,NULL); if(!zTo){ zTo = json_command_arg(3); } if(!zTo){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Required 'v2' parameter is missing."); return NULL; } nContext = json_find_option_int("context",NULL,"c",5); doSBS = json_find_option_bool("sbs",NULL,"y",0); doHtml = json_find_option_bool("html",NULL,"h",0); v = json_generate_diff(zFrom, zTo, nContext, doSBS, doHtml); if(!v){ if(!g.json.resultCode){ json_set_err(FSL_JSON_E_UNKNOWN, "Generating diff failed for unknown reason."); } return NULL; } pay = cson_new_object(); cson_object_set(pay, "from", json_new_string(zFrom)); cson_object_set(pay, "to", json_new_string(zTo)); cson_object_set(pay, "diff", v); v = 0; return pay ? cson_object_value(pay) : NULL; }
/* ** Impl of /json/user/save. */ static cson_value * json_user_save(){ /* try to get user info from GET/CLI args and construct a JSON form of it... */ cson_object * u = cson_new_object(); char const * str = NULL; char b = -1; int i = -1; int uid = -1; cson_value * payload = NULL; /* String properties... */ #define PROP(LK,SK) str = json_find_option_cstr(LK,NULL,SK); \ if(str){ cson_object_set(u, LK, json_new_string(str)); } (void)0 PROP("name","n"); PROP("password","p"); PROP("info","i"); PROP("capabilities","c"); #undef PROP /* Boolean properties... */ #define PROP(LK,DFLT) b = json_find_option_bool(LK,NULL,NULL,DFLT); \ if(DFLT!=b){ cson_object_set(u, LK, cson_value_new_bool(b)); } (void)0 PROP("forceLogout",-1); #undef PROP #define PROP(LK,DFLT) i = json_find_option_int(LK,NULL,NULL,DFLT); \ if(DFLT != i){ cson_object_set(u, LK, cson_value_new_integer(i)); } (void)0 PROP("uid",-99); #undef PROP if( g.json.reqPayload.o ){ cson_object_merge( u, g.json.reqPayload.o, CSON_MERGE_NO_RECURSE ); } json_user_update_from_json( u ); if(!g.json.resultCode){ uid = cson_value_get_integer( cson_object_get(u, "uid") ); assert((uid>0) && "Something went wrong in json_user_update_from_json()"); payload = json_load_user_by_id(uid); } cson_free_object(u); return payload; }
/* ** Impl of /json/dir. 98% of it was taken directly ** from browse.c::page_dir() */ static cson_value * json_page_dir_list(){ cson_object * zPayload = NULL; /* return value */ cson_array * zEntries = NULL; /* accumulated list of entries. */ cson_object * zEntry = NULL; /* a single dir/file entry. */ cson_array * keyStore = NULL; /* garbage collector for shared strings. */ cson_string * zKeyName = NULL; cson_string * zKeySize = NULL; cson_string * zKeyIsDir = NULL; cson_string * zKeyUuid = NULL; cson_string * zKeyTime = NULL; cson_string * zKeyRaw = NULL; char * zD = NULL; char const * zDX = NULL; int nD; char * zUuid = NULL; char const * zCI = NULL; Manifest * pM = NULL; Stmt q = empty_Stmt; int rid = 0; if( !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } zCI = json_find_option_cstr("checkin",NULL,"ci" ); /* If a specific check-in is requested, fetch and parse it. If the ** specific check-in does not exist, clear zCI. zCI==0 will cause all ** files from all check-ins to be displayed. */ if( zCI && *zCI ){ pM = manifest_get_by_name(zCI, &rid); if( pM ){ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); }else{ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, "Checkin name [%s] is unresolved.", zCI); return NULL; } } /* Jump through some hoops to find the directory name... */ zDX = json_find_option_cstr("name",NULL,NULL); if(!zDX && !g.isHTTP){ zDX = json_command_arg(g.json.dispatchDepth+1); } if(zDX && (!*zDX || (0==strcmp(zDX,"/")))){ zDX = NULL; } zD = zDX ? fossil_strdup(zDX) : NULL; nD = zD ? strlen(zD)+1 : 0; while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); /* Compute the temporary table "localfiles" containing the names ** of all files and subdirectories in the zD[] directory. ** ** Subdirectory names begin with "/". This causes them to sort ** first and it also gives us an easy way to distinguish files ** from directories in the loop that follows. */ if( zCI ){ Stmt ins; ManifestFile *pFile; ManifestFile *pPrev = 0; int nPrev = 0; int c; db_multi_exec( "CREATE TEMP TABLE json_dir_files(" " n UNIQUE NOT NULL," /* file name */ " fn UNIQUE NOT NULL," /* full file name */ " u DEFAULT NULL," /* file uuid */ " sz DEFAULT -1," /* file size */ " mtime DEFAULT NULL" /* file mtime in unix epoch format */ ");" ); db_prepare(&ins, "INSERT OR IGNORE INTO json_dir_files (n,fn,u,sz,mtime) " "SELECT" " pathelement(:path,0)," " CASE WHEN %Q IS NULL THEN '' ELSE %Q||'/' END ||:abspath," " a.uuid," " a.size," " CAST(strftime('%%s',e.mtime) AS INTEGER) " "FROM" " mlink m, " " event e," " blob a," " blob b " "WHERE" " e.objid=m.mid" " AND a.rid=m.fid"/*FILE artifact*/ " AND b.rid=m.mid"/*CHECKIN artifact*/ " AND a.uuid=:uuid", zD, zD ); manifest_file_rewind(pM); while( (pFile = manifest_file_next(pM,0))!=0 ){ if( nD>0 && ((pFile->zName[nD-1]!='/') || (0!=memcmp(pFile->zName, zD, nD-1))) ){ continue; } /*printf("zD=%s, nD=%d, pFile->zName=%s\n", zD, nD, pFile->zName);*/ if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/') ){ continue; } db_bind_text( &ins, ":path", &pFile->zName[nD] ); db_bind_text( &ins, ":abspath", &pFile->zName[nD] ); db_bind_text( &ins, ":uuid", pFile->zUuid ); db_step(&ins); db_reset(&ins); pPrev = pFile; for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} if( c=='/' ) nPrev++; } db_finalize(&ins); }else if( zD && *zD ){ db_multi_exec( "CREATE TEMP VIEW json_dir_files AS" " SELECT DISTINCT(pathelement(name,%d)) AS n," " %Q||'/'||name AS fn," " NULL AS u, NULL AS sz, NULL AS mtime" " FROM filename" " WHERE name GLOB '%q/*'" " GROUP BY n", nD, zD, zD ); }else{ db_multi_exec( "CREATE TEMP VIEW json_dir_files" " AS SELECT DISTINCT(pathelement(name,0)) AS n, NULL AS fn" " FROM filename" ); } if(zCI){ db_prepare( &q, "SELECT" " n as name," " fn as fullname," " u as uuid," " sz as size," " mtime as mtime " "FROM json_dir_files ORDER BY n"); }else{/* UUIDs are all NULL. */ db_prepare( &q, "SELECT n, fn FROM json_dir_files ORDER BY n"); } zKeyName = cson_new_string("name",4); zKeyUuid = cson_new_string("uuid",4); zKeyIsDir = cson_new_string("isDir",5); keyStore = cson_new_array(); cson_array_append( keyStore, cson_string_value(zKeyName) ); cson_array_append( keyStore, cson_string_value(zKeyUuid) ); cson_array_append( keyStore, cson_string_value(zKeyIsDir) ); if( zCI ){ zKeySize = cson_new_string("size",4); cson_array_append( keyStore, cson_string_value(zKeySize) ); zKeyTime = cson_new_string("timestamp",9); cson_array_append( keyStore, cson_string_value(zKeyTime) ); zKeyRaw = cson_new_string("downloadPath",12); cson_array_append( keyStore, cson_string_value(zKeyRaw) ); } zPayload = cson_new_object(); cson_object_set_s( zPayload, zKeyName, json_new_string((zD&&*zD) ? zD : "/") ); if( zUuid ){ cson_object_set( zPayload, "checkin", json_new_string(zUuid) ); } while( (SQLITE_ROW==db_step(&q)) ){ cson_value * name = NULL; char const * n = db_column_text(&q,0); char const isDir = ('/'==*n); zEntry = cson_new_object(); if(!zEntries){ zEntries = cson_new_array(); cson_object_set( zPayload, "entries", cson_array_value(zEntries) ); } cson_array_append(zEntries, cson_object_value(zEntry) ); if(isDir){ name = json_new_string( n+1 ); cson_object_set_s(zEntry, zKeyIsDir, cson_value_true() ); } else{ name = json_new_string( n ); } cson_object_set_s(zEntry, zKeyName, name ); if( zCI && !isDir){ /* Don't add the uuid/size for dir entries - that data refers to one of the files in that directory :/. Entries with no --checkin may refer to N versions, and therefore we cannot associate a single size and uuid with them (and fetching all would be overkill for most use cases). */ char const * fullName = db_column_text(&q,1); char const * u = db_column_text(&q,2); sqlite_int64 const sz = db_column_int64(&q,3); sqlite_int64 const ts = db_column_int64(&q,4); cson_object_set_s(zEntry, zKeyUuid, json_new_string( u ) ); cson_object_set_s(zEntry, zKeySize, cson_value_new_integer( (cson_int_t)sz )); cson_object_set_s(zEntry, zKeyTime, cson_value_new_integer( (cson_int_t)ts )); cson_object_set_s(zEntry, zKeyRaw, json_new_string_f("/raw/%T?name=%t", fullName, u)); } } db_finalize(&q); if(pM){ manifest_destroy(pM); } cson_free_array( keyStore ); free( zUuid ); free( zD ); return cson_object_value(zPayload); }
/* ** Impl for /json/report/run ** ** Options/arguments: ** ** report=int (CLI: -report # or -r #) is the report number to run. ** ** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands. ** ** format=a|o Specifies result format: a=each row is an arry, o=each ** row is an object. Default=o. */ static cson_value * json_report_run(){ int nReport; Stmt q = empty_Stmt; cson_object * pay = NULL; cson_array * tktList = NULL; char const * zFmt; char * zTitle = NULL; Blob sql = empty_blob; int limit = 0; cson_value * colNames = NULL; int i; if(!g.perm.RdTkt){ json_set_err(FSL_JSON_E_DENIED, "Requires 'r' privileges."); return NULL; } nReport = json_report_get_number(3); if(nReport <=0){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing or invalid 'number' (-n) parameter."); goto error; } zFmt = json_find_option_cstr2("format",NULL,"f",3); if(!zFmt) zFmt = "o"; db_prepare(&q, "SELECT sqlcode, " " title" " FROM reportfmt" " WHERE rn=%d", nReport); if(SQLITE_ROW != db_step(&q)){ json_set_err(FSL_JSON_E_INVALID_ARGS, "Report number %d not found.", nReport); db_finalize(&q); goto error; } limit = json_find_option_int("limit",NULL,"n",-1); /* Copy over report's SQL...*/ blob_append(&sql, db_column_text(&q,0), -1); zTitle = mprintf("%s", db_column_text(&q,1)); db_finalize(&q); db_prepare(&q, "%s", blob_str(&sql)); /** Build the response... */ pay = cson_new_object(); cson_object_set(pay, "report", json_new_int(nReport)); cson_object_set(pay, "title", json_new_string(zTitle)); if(limit>0){ cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit)); } free(zTitle); zTitle = NULL; if(g.perm.TktFmt){ cson_object_set(pay, "sqlcode", cson_value_new_string(blob_str(&sql), (unsigned int)blob_size(&sql))); } blob_reset(&sql); colNames = cson_sqlite3_column_names(q.pStmt); cson_object_set( pay, "columnNames", colNames); for( i = 0 ; ((limit>0) ?(i < limit) : 1) && (SQLITE_ROW == db_step(&q)); ++i){ cson_value * row = ('a'==*zFmt) ? cson_sqlite3_row_to_array(q.pStmt) : cson_sqlite3_row_to_object2(q.pStmt, cson_value_get_array(colNames)); ; if(row && !tktList){ tktList = cson_new_array(); } cson_array_append(tktList, row); } db_finalize(&q); cson_object_set(pay, "tickets", tktList ? cson_array_value(tktList) : cson_value_null()); goto end; error: assert(0 != g.json.resultCode); cson_value_free( cson_object_value(pay) ); pay = NULL; end: return pay ? cson_object_value(pay) : NULL; }
/* ** Implementation of the /json/status page. ** */ cson_value * json_page_status(){ Stmt q = empty_Stmt; cson_object * oPay; /*cson_object * files;*/ int vid, nErr = 0; cson_object * tmpO; char * zTmp; i64 iMtime; cson_array * aFiles; if(!db_open_local(0)){ json_set_err(FSL_JSON_E_DB_NEEDS_CHECKOUT, NULL); return NULL; } oPay = cson_new_object(); cson_object_set(oPay, "repository", json_new_string(db_repository_filename())); cson_object_set(oPay, "localRoot", json_new_string(g.zLocalRoot)); vid = db_lget_int("checkout", 0); if(!vid){ json_set_err( FSL_JSON_E_UNKNOWN, "Can this even happen?" ); return 0; } /* TODO: dupe show_common_info() state */ tmpO = cson_new_object(); cson_object_set(oPay, "checkout", cson_object_value(tmpO)); zTmp = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); cson_object_set(tmpO, "uuid", json_new_string(zTmp) ); free(zTmp); cson_object_set( tmpO, "tags", json_tags_for_checkin_rid(vid, 0) ); /* FIXME: optimize the datetime/timestamp queries into 1 query. */ zTmp = db_text(0, "SELECT datetime(mtime) || " "' UTC' FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "datetime", json_new_string(zTmp)); free(zTmp); iMtime = db_int64(0, "SELECT CAST(strftime('%%s',mtime) AS INTEGER) " "FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "timestamp", cson_value_new_integer((cson_int_t)iMtime)); #if 0 /* TODO: add parent artifact info */ tmpO = cson_new_object(); cson_object_set( oPay, "parent", cson_object_value(tmpO) ); cson_object_set( tmpO, "uuid", TODO ); cson_object_set( tmpO, "timestamp", TODO ); #endif /* Now get the list of non-pristine files... */ aFiles = cson_new_array(); cson_object_set( oPay, "files", cson_array_value( aFiles ) ); db_prepare(&q, "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)" " FROM vfile " " WHERE is_selected(id)" " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1" ); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3)==0; int isRenamed = db_column_int(&q,4); cson_object * oFile; char const * zStatus = "???"; char * zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); if( isDeleted ){ zStatus = "deleted"; }else if( isNew ){ zStatus = "new" /* maintenance reminder: MUST come BEFORE the isChnged checks. */; }else if( isRenamed ){ zStatus = "renamed"; }else if( !file_wd_isfile_or_link(zFullName) ){ if( file_access(zFullName, F_OK)==0 ){ zStatus = "notAFile"; ++nErr; }else{ zStatus = "missing"; ++nErr; } }else if( 2==isChnged ){ zStatus = "updatedByMerge"; }else if( 3==isChnged ){ zStatus = "addedByMerge"; }else if( 4==isChnged ){ zStatus = "updatedByIntegrate"; }else if( 5==isChnged ){ zStatus = "addedByIntegrate"; }else if( 1==isChnged ){ if( file_contains_merge_marker(zFullName) ){ zStatus = "conflict"; }else{ zStatus = "edited"; } } oFile = cson_new_object(); cson_array_append( aFiles, cson_object_value(oFile) ); /* optimization potential: move these keys into cson_strings to take advantage of refcounting. */ cson_object_set( oFile, "name", json_new_string( zPathname ) ); cson_object_set( oFile, "status", json_new_string( zStatus ) ); free(zFullName); } cson_object_set( oPay, "errorCount", json_new_int( nErr ) ); db_finalize(&q); #if 0 /* TODO: add "merged with" status. First need (A) to decide on a structure and (B) to set up some tests for the multi-merge case.*/ db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" " WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ){ const char *zLabel = "MERGED_WITH"; switch( db_column_int(&q, 1) ){ case -1: zLabel = "CHERRYPICK "; break; case -2: zLabel = "BACKOUT "; break; case -4: zLabel = "INTEGRATE "; break; } blob_append(report, zPrefix, nPrefix); blob_appendf(report, "%s %s\n", zLabel, db_column_text(&q, 0)); } db_finalize(&q); if( nErr ){ fossil_fatal("aborting due to prior errors"); } #endif return cson_object_value( oPay ); }
/* ** Impl of /json/artifact. This basically just determines the type of ** an artifact and forwards the real work to another function. */ cson_value * json_page_artifact(){ cson_object * pay = NULL; char const * zName = NULL; char const * zType = NULL; char const * zUuid = NULL; cson_value * entry = NULL; Blob uuid = empty_blob; int rc; int rid = 0; ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0]; zName = json_find_option_cstr2("name", NULL, NULL, g.json.dispatchDepth+1); if(!zName || !*zName) { json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing 'name' argument."); return NULL; } if( validate16(zName, strlen(zName)) ){ if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ zType = "ticket"; goto handle_entry; } if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){ zType = "tag"; goto handle_entry; } } blob_set(&uuid,zName); rc = name_to_uuid(&uuid,-1,"*"); /* FIXME: check for a filename if all else fails. */ if(1==rc){ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; }else if(2==rc){ g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID; goto error; } zUuid = blob_str(&uuid); rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid); if(0==rid){ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; } if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) || db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) || db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){ zType = "checkin"; goto handle_entry; }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){ zType = "wiki"; goto handle_entry; }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){ zType = "ticket"; goto handle_entry; }else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){ zType = "file"; goto handle_entry; }else{ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; } error: assert( 0 != g.json.resultCode ); goto veryend; handle_entry: pay = cson_new_object(); assert( (NULL != zType) && "Internal dispatching error." ); for( ; dispatcher->name; ++dispatcher ){ if(0!=strcmp(dispatcher->name, zType)){ continue; }else{ entry = (*dispatcher->func)(pay, rid); break; } } if(!g.json.resultCode){ assert( NULL != entry ); assert( NULL != zType ); cson_object_set( pay, "type", json_new_string(zType) ); cson_object_set( pay, "uuid", json_new_string(zUuid) ); /*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/ /*cson_object_set( pay, "rid", cson_value_new_integer(rid) );*/ if(cson_value_is_object(entry) && (cson_value_get_object(entry) != pay)){ cson_object_set(pay, "artifact", entry); } } veryend: blob_reset(&uuid); if(g.json.resultCode && pay){ cson_free_object(pay); pay = NULL; } return cson_object_value(pay); }
static cson_value * json_timeline_branch(){ cson_value * pay = NULL; Blob sql = empty_blob; Stmt q = empty_Stmt; int limit = 0; if(!g.perm.Read){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } json_timeline_temp_table(); blob_append(&sql, "SELECT" " blob.rid AS rid," " uuid AS uuid," " CAST(strftime('%s',event.mtime) AS INTEGER) as timestamp," " coalesce(ecomment, comment) as comment," " coalesce(euser, user) as user," " blob.rid IN leaf as isLeaf," " bgcolor as bgColor" " FROM event JOIN blob" " WHERE blob.rid=event.objid", -1); blob_append_sql(&sql, " AND event.type='ci'" " AND blob.rid IN (SELECT rid FROM tagxref" " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" " ORDER BY event.mtime DESC", TAG_BRANCH); limit = json_timeline_limit(20); if(limit>0){ blob_append_sql(&sql," LIMIT %d ",limit); } db_prepare(&q,"%s", blob_sql_text(&sql)); blob_reset(&sql); pay = json_stmt_to_array_of_obj(&q, NULL); db_finalize(&q); assert(NULL != pay); if(pay){ /* get the array-form tags of each record. */ cson_string * tags = cson_new_string("tags",4); cson_string * isLeaf = cson_new_string("isLeaf",6); cson_array * ar = cson_value_get_array(pay); cson_object * outer = NULL; unsigned int i = 0; unsigned int len = cson_array_length_get(ar); cson_value_add_reference( cson_string_value(tags) ); cson_value_add_reference( cson_string_value(isLeaf) ); for( ; i < len; ++i ){ cson_object * row = cson_value_get_object(cson_array_get(ar,i)); int rid = cson_value_get_integer(cson_object_get(row,"rid")); assert( rid > 0 ); cson_object_set_s(row, tags, json_tags_for_checkin_rid(rid,0)); cson_object_set_s(row, isLeaf, json_value_to_bool(cson_object_get(row,"isLeaf"))); cson_object_set(row, "rid", NULL) /* remove rid - we don't really want it to be public */; } cson_value_free( cson_string_value(tags) ); cson_value_free( cson_string_value(isLeaf) ); /* now we wrap the payload in an outer shell, for consistency with other /json/timeline/xyz APIs... */ outer = cson_new_object(); if(limit>0){ cson_object_set( outer, "limit", json_new_int(limit) ); } cson_object_set( outer, "timeline", pay ); pay = cson_object_value(outer); } return pay; }