#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();
static cson_value * json_tag_cancel();
static cson_value * json_tag_find();
static cson_value * json_tag_list();
/*
** 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(){
return json_page_dispatch_helper(&JsonPageDefs_Tag[0]);
}
/*
** Impl of /json/tag/add.
*/
static cson_value * json_tag_add(){
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(){
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(){
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(){
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 */