| | @@ -29,12 +29,16 @@ |
| 29 | 29 | ** specialized to return a JSON form of one type of artifact. |
| 30 | 30 | ** |
| 31 | 31 | ** Implementations may assert() that rid refers to requested artifact |
| 32 | 32 | ** type, since mismatches in the artifact types come from |
| 33 | 33 | ** json_page_artifact() as opposed to client data. |
| 34 | +** |
| 35 | +** The pParent parameter points to the response payload object. It |
| 36 | +** _may_ be used to populate "top-level" information in the response |
| 37 | +** payload, but normally this is neither necessary nor desired. |
| 34 | 38 | */ |
| 35 | | -typedef cson_value * (*artifact_f)( int rid ); |
| 39 | +typedef cson_value * (*artifact_f)( cson_object * pParent, int rid ); |
| 36 | 40 | |
| 37 | 41 | /* |
| 38 | 42 | ** Internal per-artifact-type dispatching helper. |
| 39 | 43 | */ |
| 40 | 44 | typedef struct ArtifactDispatchEntry { |
| | @@ -160,11 +164,11 @@ |
| 160 | 164 | if(tmpV){ |
| 161 | 165 | SET("tags",tmpV); |
| 162 | 166 | } |
| 163 | 167 | |
| 164 | 168 | if( showFiles ){ |
| 165 | | - tmpV = json_get_changed_files(rid); |
| 169 | + tmpV = json_get_changed_files(rid, 1); |
| 166 | 170 | if(tmpV){ |
| 167 | 171 | SET("files",tmpV); |
| 168 | 172 | } |
| 169 | 173 | } |
| 170 | 174 | |
| | @@ -175,11 +179,11 @@ |
| 175 | 179 | } |
| 176 | 180 | |
| 177 | 181 | /* |
| 178 | 182 | ** Very incomplete/incorrect impl of /json/artifact/TICKET_ID. |
| 179 | 183 | */ |
| 180 | | -cson_value * json_artifact_ticket( int rid ){ |
| 184 | +cson_value * json_artifact_ticket( cson_object * zParent, int rid ){ |
| 181 | 185 | cson_object * pay = NULL; |
| 182 | 186 | Manifest *pTktChng = NULL; |
| 183 | 187 | static cson_value * eventTypeLabel = NULL; |
| 184 | 188 | if(! g.perm.RdTkt ){ |
| 185 | 189 | g.json.resultCode = FSL_JSON_E_DENIED; |
| | @@ -205,16 +209,22 @@ |
| 205 | 209 | } |
| 206 | 210 | |
| 207 | 211 | /* |
| 208 | 212 | ** Sub-impl of /json/artifact for checkins. |
| 209 | 213 | */ |
| 210 | | -static cson_value * json_artifact_ci( int rid ){ |
| 214 | +static cson_value * json_artifact_ci( cson_object * zParent, int rid ){ |
| 211 | 215 | if(!g.perm.Read){ |
| 212 | 216 | json_set_err( FSL_JSON_E_DENIED, "Viewing checkins requires 'o' privileges." ); |
| 213 | 217 | return NULL; |
| 214 | 218 | }else{ |
| 215 | | - return json_artifact_for_ci(rid, 1); |
| 219 | + cson_value * artV = json_artifact_for_ci(rid, 1); |
| 220 | + cson_object * art = cson_value_get_object(artV); |
| 221 | + if(art){ |
| 222 | + cson_object_merge( zParent, art, CSON_MERGE_REPLACE ); |
| 223 | + cson_free_object(art); |
| 224 | + } |
| 225 | + return cson_object_value(zParent); |
| 216 | 226 | } |
| 217 | 227 | } |
| 218 | 228 | |
| 219 | 229 | /* |
| 220 | 230 | ** Internal mapping of /json/artifact/FOO commands/callbacks. |
| | @@ -228,27 +238,36 @@ |
| 228 | 238 | /* Final entry MUST have a NULL name. */ |
| 229 | 239 | {NULL,NULL} |
| 230 | 240 | }; |
| 231 | 241 | |
| 232 | 242 | /* |
| 233 | | -** Internal helper which returns true (non-0) if the includeContent |
| 234 | | -** (HTTP) or -content|-c flags (CLI) are set. |
| 243 | +** Internal helper which returns: |
| 244 | +** |
| 245 | +** If the "format" (CLI: -f) flag is set function returns the same as |
| 246 | +** json_wiki_get_content_format_flag(), else it returns true (non-0) |
| 247 | +** if either the includeContent (HTTP) or -content|-c boolean flags |
| 248 | +** (CLI) are set. |
| 235 | 249 | */ |
| 236 | | -static char json_artifact_include_content_flag(){ |
| 237 | | - return json_find_option_bool("includeContent","content","c",0); |
| 250 | +static char json_artifact_get_content_format_flag(){ |
| 251 | + enum { MagicValue = -9 }; |
| 252 | + char contentFormat = json_wiki_get_content_format_flag(MagicValue); |
| 253 | + if(MagicValue == contentFormat){ |
| 254 | + contentFormat = json_find_option_bool("includeContent","content","c",0) /* deprecated */ ? -1 : 0; |
| 255 | + } |
| 256 | + return contentFormat; |
| 238 | 257 | } |
| 239 | 258 | |
| 240 | | -cson_value * json_artifact_wiki(int rid){ |
| 259 | +extern char json_wiki_get_content_format_flag( char defaultValue ) /* json_wiki.c */; |
| 260 | + |
| 261 | +cson_value * json_artifact_wiki(cson_object * zParent, int rid){ |
| 241 | 262 | if( ! g.perm.RdWiki ){ |
| 242 | 263 | json_set_err(FSL_JSON_E_DENIED, |
| 243 | 264 | "Requires 'j' privileges."); |
| 244 | 265 | return NULL; |
| 245 | 266 | }else{ |
| 246 | | - char contentFormat = json_wiki_get_content_format_flag(-9); |
| 247 | | - if(-9 == contentFormat){ |
| 248 | | - contentFormat = json_artifact_include_content_flag() ? -1 : 0; |
| 249 | | - } |
| 267 | + enum { MagicValue = -9 }; |
| 268 | + char const contentFormat = json_artifact_get_content_format_flag(); |
| 250 | 269 | return json_get_wiki_page_by_rid(rid, contentFormat); |
| 251 | 270 | } |
| 252 | 271 | } |
| 253 | 272 | |
| 254 | 273 | /* |
| | @@ -266,48 +285,83 @@ |
| 266 | 285 | : (isDel |
| 267 | 286 | ? "removed" |
| 268 | 287 | : "modified"); |
| 269 | 288 | } |
| 270 | 289 | |
| 271 | | -cson_value * json_artifact_file(int rid){ |
| 290 | +cson_value * json_artifact_file(cson_object * zParent, int rid){ |
| 272 | 291 | cson_object * pay = NULL; |
| 273 | 292 | Stmt q = empty_Stmt; |
| 274 | 293 | cson_array * checkin_arr = NULL; |
| 275 | | - |
| 294 | + char contentFormat; |
| 295 | + i64 contentSize = -1; |
| 296 | + char * parentUuid; |
| 276 | 297 | if( ! g.perm.Read ){ |
| 277 | 298 | json_set_err(FSL_JSON_E_DENIED, |
| 278 | 299 | "Requires 'o' privileges."); |
| 279 | 300 | return NULL; |
| 280 | 301 | } |
| 281 | 302 | |
| 282 | | - pay = cson_new_object(); |
| 303 | + pay = zParent; |
| 283 | 304 | |
| 284 | | - if( json_artifact_include_content_flag() ){ |
| 305 | + contentFormat = json_artifact_get_content_format_flag(); |
| 306 | + if( 0 != contentFormat ){ |
| 285 | 307 | Blob content = empty_blob; |
| 286 | 308 | const char *zMime; |
| 309 | + char const * zFormat = (contentFormat<1) ? "raw" : "html"; |
| 287 | 310 | content_get(rid, &content); |
| 288 | 311 | zMime = mimetype_from_content(&content); |
| 289 | | - cson_object_set(pay, "contentType", |
| 312 | + cson_object_set(zParent, "contentType", |
| 290 | 313 | json_new_string(zMime ? zMime : "text/plain")); |
| 291 | | - cson_object_set(pay, "size", json_new_int( blob_size(&content)) ); |
| 292 | | - if(!zMime){ |
| 293 | | - cson_object_set(pay, "content", |
| 314 | + if(!zMime){/* text/plain */ |
| 315 | + if(0 < blob_size(&content)){ |
| 316 | + if( 0 < contentFormat ){/*HTML-size it*/ |
| 317 | + Blob html = empty_blob; |
| 318 | + wiki_convert(&content, &html, 0); |
| 319 | + assert( blob_size(&content) < blob_size(&html) ); |
| 320 | + blob_swap( &html, &content ); |
| 321 | + assert( blob_size(&content) > blob_size(&html) ); |
| 322 | + blob_reset( &html ); |
| 323 | + }/*else as-is*/ |
| 324 | + } |
| 325 | + cson_object_set(zParent, "content", |
| 294 | 326 | cson_value_new_string(blob_str(&content), |
| 295 | 327 | (unsigned int)blob_size(&content))); |
| 296 | | - } |
| 328 | + }/*else binary: ignore*/ |
| 329 | + contentSize = blob_size(&content); |
| 330 | + cson_object_set(zParent, "contentSize", json_new_int(contentSize) ); |
| 331 | + cson_object_set(zParent, "contentFormat", json_new_string(zFormat) ); |
| 297 | 332 | blob_reset(&content); |
| 298 | 333 | } |
| 334 | + contentSize = db_int64(-1, "SELECT size FROM blob WHERE rid=%d", rid); |
| 335 | + assert( -1 < contentSize ); |
| 336 | + cson_object_set(zParent, "size", json_new_int(contentSize) ); |
| 299 | 337 | |
| 338 | + parentUuid = db_text(NULL, |
| 339 | + "SELECT DISTINCT p.uuid " |
| 340 | + "FROM blob p, blob f, mlink m " |
| 341 | + "WHERE m.pid=p.rid " |
| 342 | + "AND m.fid=f.rid " |
| 343 | + "AND f.rid=%d", |
| 344 | + rid |
| 345 | + ); |
| 346 | + if(parentUuid){ |
| 347 | + cson_object_set( zParent, "parent", json_new_string(parentUuid) ); |
| 348 | + fossil_free(parentUuid); |
| 349 | + } |
| 350 | + |
| 351 | + /* Find checkins associated with this file... */ |
| 300 | 352 | db_prepare(&q, |
| 301 | 353 | "SELECT filename.name AS name, " |
| 302 | 354 | " (mlink.pid==0) AS isNew," |
| 303 | 355 | " (mlink.fid==0) AS isDel," |
| 304 | 356 | " cast(strftime('%%s',event.mtime) as int) AS timestamp," |
| 305 | 357 | " coalesce(event.ecomment,event.comment) as comment," |
| 306 | 358 | " coalesce(event.euser,event.user) as user," |
| 307 | | - " a.size AS size," |
| 308 | | - " b.uuid as uuid, " |
| 359 | +#if 0 |
| 360 | + " a.size AS size," /* same for all checkins. */ |
| 361 | +#endif |
| 362 | + " b.uuid as checkin, " |
| 309 | 363 | #if 0 |
| 310 | 364 | " mlink.mperm as mperm," |
| 311 | 365 | #endif |
| 312 | 366 | " coalesce((SELECT value FROM tagxref" |
| 313 | 367 | " WHERE tagid=%d AND tagtype>0 AND " |
| | @@ -326,10 +380,11 @@ |
| 326 | 380 | */ |
| 327 | 381 | checkin_arr = cson_new_array(); |
| 328 | 382 | cson_object_set(pay, "checkins", cson_array_value(checkin_arr)); |
| 329 | 383 | while( (SQLITE_ROW==db_step(&q) ) ){ |
| 330 | 384 | cson_object * row = cson_value_get_object(cson_sqlite3_row_to_object(q.pStmt)); |
| 385 | + /* FIXME: move this isNew/isDel stuff into an SQL CASE statement. */ |
| 331 | 386 | char const isNew = cson_value_get_bool(cson_object_get(row,"isNew")); |
| 332 | 387 | char const isDel = cson_value_get_bool(cson_object_get(row,"isDel")); |
| 333 | 388 | cson_object_set(row, "isNew", NULL); |
| 334 | 389 | cson_object_set(row, "isDel", NULL); |
| 335 | 390 | cson_object_set(row, "state", |
| | @@ -371,10 +426,11 @@ |
| 371 | 426 | goto handle_entry; |
| 372 | 427 | } |
| 373 | 428 | } |
| 374 | 429 | blob_set(&uuid,zName); |
| 375 | 430 | rc = name_to_uuid(&uuid,-1,"*"); |
| 431 | + /* FIXME: check for a filename if all else fails. */ |
| 376 | 432 | if(1==rc){ |
| 377 | 433 | g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; |
| 378 | 434 | goto error; |
| 379 | 435 | }else if(2==rc){ |
| 380 | 436 | g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID; |
| | @@ -411,32 +467,36 @@ |
| 411 | 467 | error: |
| 412 | 468 | assert( 0 != g.json.resultCode ); |
| 413 | 469 | goto veryend; |
| 414 | 470 | |
| 415 | 471 | handle_entry: |
| 472 | + pay = cson_new_object(); |
| 416 | 473 | assert( (NULL != zType) && "Internal dispatching error." ); |
| 417 | 474 | for( ; dispatcher->name; ++dispatcher ){ |
| 418 | 475 | if(0!=strcmp(dispatcher->name, zType)){ |
| 419 | 476 | continue; |
| 420 | 477 | }else{ |
| 421 | | - entry = (*dispatcher->func)(rid); |
| 478 | + entry = (*dispatcher->func)(pay, rid); |
| 422 | 479 | break; |
| 423 | 480 | } |
| 424 | 481 | } |
| 425 | 482 | if(!g.json.resultCode){ |
| 426 | 483 | assert( NULL != entry ); |
| 427 | 484 | assert( NULL != zType ); |
| 428 | | - pay = cson_new_object(); |
| 429 | 485 | cson_object_set( pay, "type", json_new_string(zType) ); |
| 430 | | - /*cson_object_set( pay, "uuid", json_new_string(zUuid) );*/ |
| 431 | | - cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) ); |
| 486 | + cson_object_set( pay, "uuid", json_new_string(zUuid) ); |
| 487 | + /*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/ |
| 432 | 488 | /*cson_object_set( pay, "rid", cson_value_new_integer(rid) );*/ |
| 433 | | - if(entry){ |
| 489 | + if(cson_value_is_object(entry) && (cson_value_get_object(entry) != pay)){ |
| 434 | 490 | cson_object_set(pay, "artifact", entry); |
| 435 | 491 | } |
| 436 | 492 | } |
| 437 | 493 | veryend: |
| 438 | 494 | blob_reset(&uuid); |
| 495 | + if(g.json.resultCode && pay){ |
| 496 | + cson_free_object(pay); |
| 497 | + pay = NULL; |
| 498 | + } |
| 439 | 499 | return cson_object_value(pay); |
| 440 | 500 | } |
| 441 | 501 | |
| 442 | 502 | #endif /* FOSSIL_ENABLE_JSON */ |
| 443 | 503 | |