diff options
Diffstat (limited to 'src/json_tag.c')
| -rw-r--r-- | src/json_tag.c | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/src/json_tag.c b/src/json_tag.c new file mode 100644 index 0000000..611c0fd --- /dev/null +++ b/src/json_tag.c @@ -0,0 +1,479 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_tag.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + +static cson_value * json_tag_add(void); +static cson_value * json_tag_cancel(void); +static cson_value * json_tag_find(void); +static cson_value * json_tag_list(void); +/* +** Mapping of /json/tag/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Tag[] = { +{"add", json_tag_add, 0}, +{"cancel", json_tag_cancel, 0}, +{"find", json_tag_find, 0}, +{"list", json_tag_list, 0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + +/* +** Implements the /json/tag family of pages/commands. +** +*/ +cson_value * json_page_tag(void){ + return json_page_dispatch_helper(&JsonPageDefs_Tag[0]); +} + + +/* +** Impl of /json/tag/add. +*/ +static cson_value * json_tag_add(void){ + cson_value * payV = NULL; + cson_object * pay = NULL; + char const * zName = NULL; + char const * zCheckin = NULL; + char fRaw = 0; + char fPropagate = 0; + char const * zValue = NULL; + const char *zPrefix = NULL; + + if( !g.perm.Write ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'i' permissions."); + return NULL; + } + fRaw = json_find_option_bool("raw",NULL,NULL,0); + fPropagate = json_find_option_bool("propagate",NULL,NULL,0); + zName = json_find_option_cstr("name",NULL,NULL); + zPrefix = fRaw ? "" : "sym-"; + if(!zName || !*zName){ + if(!fossil_has_json()){ + zName = json_command_arg(3); + } + if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + } + + zCheckin = json_find_option_cstr("checkin",NULL,NULL); + if( !zCheckin ){ + if(!fossil_has_json()){ + zCheckin = json_command_arg(4); + } + if(!zCheckin || !*zCheckin){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'checkin' parameter is missing."); + return NULL; + } + } + + + zValue = json_find_option_cstr("value",NULL,NULL); + if(!zValue && !fossil_has_json()){ + zValue = json_command_arg(5); + } + + db_begin_transaction(); + tag_add_artifact(zPrefix, zName, zCheckin, zValue, + 1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/); + db_end_transaction(0); + + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + cson_object_set(pay, "name", json_new_string(zName) ); + cson_object_set(pay, "value", (zValue&&*zValue) + ? json_new_string(zValue) + : cson_value_null()); + cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate)); + cson_object_set(pay, "raw", cson_value_new_bool(fRaw)); + { + Blob uu = empty_blob; + int rc; + blob_append(&uu, zName, -1); + rc = name_to_uuid(&uu, 9, "*"); + if(0!=rc){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Could not convert name back to artifact hash!"); + blob_reset(&uu); + goto error; + } + cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu))); + blob_reset(&uu); + } + + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + return payV; +} + + +/* +** Impl of /json/tag/cancel. +*/ +static cson_value * json_tag_cancel(void){ + char const * zName = NULL; + char const * zCheckin = NULL; + char fRaw = 0; + const char *zPrefix = NULL; + + if( !g.perm.Write ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'i' permissions."); + return NULL; + } + + fRaw = json_find_option_bool("raw",NULL,NULL,0); + zPrefix = fRaw ? "" : "sym-"; + zName = json_find_option_cstr("name",NULL,NULL); + if(!zName || !*zName){ + if(!fossil_has_json()){ + zName = json_command_arg(3); + } + if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + } + + zCheckin = json_find_option_cstr("checkin",NULL,NULL); + if( !zCheckin ){ + if(!fossil_has_json()){ + zCheckin = json_command_arg(4); + } + if(!zCheckin || !*zCheckin){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'checkin' parameter is missing."); + return NULL; + } + } + /* FIXME?: verify that the tag is currently active. We have no real + error case unless we do that. + */ + db_begin_transaction(); + tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0); + db_end_transaction(0); + return NULL; +} + + +/* +** Impl of /json/tag/find. +*/ +static cson_value * json_tag_find(void){ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_value * listV = NULL; + cson_array * list = NULL; + char const * zName = NULL; + char const * zType = NULL; + char const * zType2 = NULL; + char fRaw = 0; + Stmt q = empty_Stmt; + int limit = 0; + int tagid = 0; + + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + zName = json_find_option_cstr("name",NULL,NULL); + if(!zName || !*zName){ + if(!fossil_has_json()){ + zName = json_command_arg(3); + } + if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + } + zType = json_find_option_cstr("type",NULL,"t"); + if(!zType || !*zType){ + zType = "*"; + zType2 = zType; + }else{ + switch(*zType){ + case 'c': zType = "ci"; zType2 = "checkin"; break; + case 'e': zType = "e"; zType2 = "event"; break; + case 'w': zType = "w"; zType2 = "wiki"; break; + case 't': zType = "t"; zType2 = "ticket"; break; + } + } + + limit = json_find_option_int("limit",NULL,"n",0); + fRaw = json_find_option_bool("raw",NULL,NULL,0); + + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q", + fRaw ? "" : "sym-", + zName); + + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + cson_object_set(pay, "name", json_new_string(zName)); + cson_object_set(pay, "raw", cson_value_new_bool(fRaw)); + cson_object_set(pay, "type", json_new_string(zType2)); + cson_object_set(pay, "limit", json_new_int(limit)); + +#if 1 + if( tagid<=0 ){ + cson_object_set(pay,"artifacts", cson_value_null()); + json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found."); + return payV; + } +#endif + + if( fRaw ){ + db_prepare(&q, + "SELECT blob.uuid FROM tagxref, blob" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" + " AND tagxref.tagtype>0" + " AND blob.rid=tagxref.rid" + "%s LIMIT %d", + zName, + (limit>0)?"":"--", limit + ); + while( db_step(&q)==SQLITE_ROW ){ + if(!listV){ + listV = cson_value_new_array(); + list = cson_value_get_array(listV); + } + cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0)); + } + db_finalize(&q); + }else{ + char const * zSqlBase = /*modified from timeline_query_for_tty()*/ + " SELECT" +#if 0 + " blob.rid AS rid," +#endif + " uuid AS uuid," + " cast(strftime('%s',event.mtime) as int) AS timestamp," + " coalesce(ecomment,comment) AS comment," + " coalesce(euser,user) AS user," + " CASE event.type" + " WHEN 'ci' THEN 'checkin'" + " WHEN 'w' THEN 'wiki'" + " WHEN 'e' THEN 'event'" + " WHEN 't' THEN 'ticket'" + " ELSE 'unknown'" + " END" + " AS eventType" + " FROM event, blob" + " WHERE blob.rid=event.objid" + ; + /* FIXME: re-add tags. */ + db_prepare(&q, + "%s" + " AND event.type GLOB '%q'" + " AND blob.rid IN (" + " SELECT rid FROM tagxref" + " WHERE tagtype>0 AND tagid=%d" + " )" + " ORDER BY event.mtime DESC" + "%s LIMIT %d", + zSqlBase /*safe-for-%s*/, zType, tagid, + (limit>0)?"":"--", limit + ); + listV = json_stmt_to_array_of_obj(&q, NULL); + db_finalize(&q); + } + + if(!listV) { + listV = cson_value_null(); + } + cson_object_set(pay, "artifacts", listV); + return payV; +} + + +/* +** Impl for /json/tag/list +** +** TODOs: +** +** Add -type TYPE (ci, w, e, t) +*/ +static cson_value * json_tag_list(void){ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_value const * tagsVal = NULL; + char const * zCheckin = NULL; + char fRaw = 0; + char fTicket = 0; + Stmt q = empty_Stmt; + + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + + fRaw = json_find_option_bool("raw",NULL,NULL,0); + fTicket = json_find_option_bool("includeTickets","tkt","t",0); + zCheckin = json_find_option_cstr("checkin",NULL,NULL); + if( !zCheckin ){ + zCheckin = json_command_arg( g.json.dispatchDepth + 1); + if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){ + zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v)); + assert(zCheckin); + } + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + cson_object_set(pay, "raw", cson_value_new_bool(fRaw) ); + if( zCheckin ){ + /** + Tags for a specific check-in. Output format: + + RAW mode: + + { + "sym-tagname": (value || null), + ...other tags... + } + + Non-raw: + + { + "tagname": (value || null), + ...other tags... + } + */ + cson_value * objV = NULL; + cson_object * obj = NULL; + int const rid = name_to_rid(zCheckin); + if(0==rid){ + json_set_err(FSL_JSON_E_UNRESOLVED_UUID, + "Could not find artifact for check-in [%s].", + zCheckin); + goto error; + } + cson_object_set(pay, "checkin", json_new_string(zCheckin)); + db_prepare(&q, + "SELECT tagname, value FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" + " AND tagtype>%d" + " ORDER BY tagname", + rid, + fRaw ? -1 : 0 + ); + while( SQLITE_ROW == db_step(&q) ){ + const char *zName = db_column_text(&q, 0); + const char *zValue = db_column_text(&q, 1); + if( fRaw==0 ){ + if( 0!=strncmp(zName, "sym-", 4) ) continue; + zName += 4; + assert( *zName ); + } + if(NULL==objV){ + objV = cson_value_new_object(); + obj = cson_value_get_object(objV); + tagsVal = objV; + cson_object_set( pay, "tags", objV ); + } + if( zValue && zValue[0] ){ + cson_object_set( obj, zName, json_new_string(zValue) ); + }else{ + cson_object_set( obj, zName, cson_value_null() ); + } + } + db_finalize(&q); + }else{/* all tags */ + /* Output format: + + RAW mode: + + ["tagname", "sym-tagname2",...] + + Non-raw: + + ["tagname", "tagname2",...] + + i don't really like the discrepancy in the format but this list + can get really long and (A) most tags don't have values, (B) i + don't want to bloat it more, and (C) cson_object_set() is O(N) + (N=current number of properties) because it uses an unsorted list + internally (for memory reasons), so this can slow down appreciably + on a long list. The culprit is really tkt- tags, as there is one + for each ticket (941 in the main fossil repo as of this writing). + */ + Blob sql = empty_blob; + cson_value * arV = NULL; + cson_array * ar = NULL; + blob_append(&sql, + "SELECT tagname FROM tag" + " WHERE EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=tag.tagid" + " AND tagtype>0)", + -1 + ); + if(!fTicket){ + blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1); + } + blob_append(&sql, + " ORDER BY tagname", -1); + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) ); + while( SQLITE_ROW == db_step(&q) ){ + const char *zName = db_column_text(&q, 0); + if(NULL==arV){ + arV = cson_value_new_array(); + ar = cson_value_get_array(arV); + cson_object_set(pay, "tags", arV); + tagsVal = arV; + } + else if( !fRaw && (0==strncmp(zName, "sym-", 4))){ + zName += 4; + assert( *zName ); + } + cson_array_append(ar, json_new_string(zName)); + } + db_finalize(&q); + } + + goto end; + error: + assert(0 != g.json.resultCode); + cson_value_free(payV); + payV = NULL; + end: + if( payV && !tagsVal ){ + cson_object_set( pay, "tags", cson_value_null() ); + } + return payV; +} +#endif /* FOSSIL_ENABLE_JSON */ |
