Fossil

Check-in [12545465]
Login

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

Overview
Comment:Implemented /json/timeline/wiki. Fixed --limit/-n handling of timeline/ci.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | json
Files: files | file ages | folders
SHA1: 12545465e98da0733d6240b7cfdb9b85e9ccb00e
User & Date: stephan 2011-09-22 20:07:04
Context
2011-09-22
23:40
Added --after/--before support to /json/timeline/ci and wiki. check-in: e01d49b2 user: stephan tags: json
20:07
Implemented /json/timeline/wiki. Fixed --limit/-n handling of timeline/ci. check-in: 12545465 user: stephan tags: json
19:14
Implemented /json/wiki/create. check-in: 1df648ab user: stephan tags: json
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ajax/index.html.

201
202
203
204
205
206
207


208
209
210
211
212
213
214
<strong>Quick-posts:</strong><br/>
<input type='button' value='HAI' onclick='TheApp.cgi.HAI()' />
<input type='button' value='version' onclick='TheApp.cgi.sendCommand("/json/version")' />
<input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat")' />
<input type='button' value='whoami' onclick='TheApp.cgi.sendCommand("/json/whoami")' />
<input type='button' value='cap' onclick='TheApp.cgi.sendCommand("/json/cap")' />
<input type='button' value='branch/list' onclick='TheApp.cgi.sendCommand("/json/branch/list")' />


<input type='button' value='wiki/list' onclick='TheApp.cgi.sendCommand("/json/wiki/list")' />
<input type='button' value='wiki/get Fossil' onclick='TheApp.cgi.sendCommand("/json/wiki/get",{"page":"Fossil"})' />

<!--
<input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' />
<input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/WhikiNews")' />
<input type='button' value='get client data' onclick='TheApp.cgi.getPageClientData("HelloWorld/whiki/WhikiCommands")' />







>
>







201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
<strong>Quick-posts:</strong><br/>
<input type='button' value='HAI' onclick='TheApp.cgi.HAI()' />
<input type='button' value='version' onclick='TheApp.cgi.sendCommand("/json/version")' />
<input type='button' value='stat' onclick='TheApp.cgi.sendCommand("/json/stat")' />
<input type='button' value='whoami' onclick='TheApp.cgi.sendCommand("/json/whoami")' />
<input type='button' value='cap' onclick='TheApp.cgi.sendCommand("/json/cap")' />
<input type='button' value='branch/list' onclick='TheApp.cgi.sendCommand("/json/branch/list")' />
<input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/timeline/ci")' />
<input type='button' value='timeline/wiki' onclick='TheApp.cgi.sendCommand("/json/timeline/wiki")' />
<input type='button' value='wiki/list' onclick='TheApp.cgi.sendCommand("/json/wiki/list")' />
<input type='button' value='wiki/get Fossil' onclick='TheApp.cgi.sendCommand("/json/wiki/get",{"page":"Fossil"})' />

<!--
<input type='button' value='get whiki' onclick='TheApp.cgi.getPages("whiki")' />
<input type='button' value='get more' onclick='TheApp.cgi.getPages("HelloWorld/WhikiNews")' />
<input type='button' value='get client data' onclick='TheApp.cgi.getPageClientData("HelloWorld/whiki/WhikiCommands")' />

Changes to src/json.c.

719
720
721
722
723
724
725




726
727
728
729
730
731
732
....
1863
1864
1865
1866
1867
1868
1869

1870
1871
1872
1873
1874

1875
1876
1877
1878
1879
1880
1881
....
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
....
1933
1934
1935
1936
1937
1938
1939














1940








1941
1942
1943
1944


1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960

1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
....
2050
2051
2052
2053
2054
2055
2056













































































































2057
2058
2059
2060
2061
2062
2063
  g.json.isJsonMode = 1;
  g.json.resultCode = 0;
  g.json.cmd.offset = -1;
  if( !g.isHTTP && g.fullHttpReply ){
    /* workaround for server mode, so we see it as CGI mode. */
    g.isHTTP = 1;
  }




  if(! g.json.post.v ){
    /* If cgi_init() reads POSTed JSON then it sets the content type.
       If it did not then we need to set it.
    */
    cgi_set_content_type(json_guess_content_type());
  }

................................................................................
      free(msg);
    }
  }
  return payV;
}

static cson_value * json_timeline_ci(unsigned int depth);

/*
** Mapping of /json/timeline/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Timeline[] = {
{"ci", json_timeline_ci, 0},

/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};

/*
** Implements the /json/timeline family of pages/commands. Far from
** complete.
................................................................................
    @ )
  ;
  db_multi_exec(zSql);
}

/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the JSON "checkin" interface.
*/
const char const * json_timeline_query_ci(void){
  /* Field order MUST match that from json_timeline_temp_table()!!! */
  static const char zBaseSql[] =
    @ SELECT
    @   blob.rid,
    @   uuid,
    @   strftime('%%s',event.mtime),
    @   datetime(event.mtime,'utc'),
................................................................................
    @   brief
    @  FROM event JOIN blob 
    @ WHERE blob.rid=event.objid
  ;
  return zBaseSql;
}
























/*
** /json/timeline/ci
**
** Far from complete.


*/
static cson_value * json_timeline_ci(unsigned int depth){
  static const int defaultLimit = 20;
  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;\
  }
................................................................................
  goto ok;
  error:
  cson_value_free(payV);
  payV = NULL;
  ok:
  return payV;
}














































































































/*
** Implements the /json/whoami page/command.
*/
static cson_value * json_page_whoami(unsigned int depth){
  cson_value * payload = NULL;
  cson_object * obj = NULL;







>
>
>
>







 







>





>







 







|

|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>

|

<
>
>


<





|







>


<


|







 







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







719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
....
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
....
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
....
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971

1972
1973
1974
1975

1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991

1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
....
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
  g.json.isJsonMode = 1;
  g.json.resultCode = 0;
  g.json.cmd.offset = -1;
  if( !g.isHTTP && g.fullHttpReply ){
    /* workaround for server mode, so we see it as CGI mode. */
    g.isHTTP = 1;
  }
  if( !g.isHTTP ){
    g.json.errorDetailParanoia = 0 /*disable error code dumb-down for CLI mode*/;
  }

  if(! g.json.post.v ){
    /* If cgi_init() reads POSTed JSON then it sets the content type.
       If it did not then we need to set it.
    */
    cgi_set_content_type(json_guess_content_type());
  }

................................................................................
      free(msg);
    }
  }
  return payV;
}

static cson_value * json_timeline_ci(unsigned int depth);
static cson_value * json_timeline_wiki(unsigned int depth);
/*
** Mapping of /json/timeline/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Timeline[] = {
{"ci", json_timeline_ci, 0},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};

/*
** Implements the /json/timeline family of pages/commands. Far from
** complete.
................................................................................
    @ )
  ;
  db_multi_exec(zSql);
}

/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the JSON interface.
*/
const char const * json_timeline_query(void){
  /* Field order MUST match that from json_timeline_temp_table()!!! */
  static const char zBaseSql[] =
    @ SELECT
    @   blob.rid,
    @   uuid,
    @   strftime('%%s',event.mtime),
    @   datetime(event.mtime,'utc'),
................................................................................
    @   brief
    @  FROM event JOIN blob 
    @ WHERE blob.rid=event.objid
  ;
  return zBaseSql;
}

/*
** Tries to figure out a timeline query length limit base on
** environment parameters. If it can it returns that value,
** else it returns some statically defined default value.
**
** Never returns a negative value. 0 means no limit.
*/
static int json_timeline_limit(){
  static const int defaultLimit = 20;
  int limit = -1;
  if( g.isHTTP ){
    limit = json_getenv_int("limit",-1);
    if(limit<0){
      limit = json_getenv_int("n",-1);
    }
  }else{/* CLI mode */
    char const * arg = find_option("limit","n",1);
    if(arg && *arg){
      limit = atoi(arg);
    }
  }
  return (limit<0) ? defaultLimit : limit;
}
/*
** Implementation of /json/timeline/ci.
**

** Still a few TODOs (like figuring out how to structure
** inheritance info).
*/
static cson_value * json_timeline_ci(unsigned int depth){

  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_value * tmp = NULL;
  cson_value * listV = NULL;
  cson_array * list = NULL;
  int limit;
  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;
  }
  limit = json_timeline_limit();
  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);

  json_timeline_temp_table();
  blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1);
  blob_append(&sql, json_timeline_query(), -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;\
  }
................................................................................
  goto ok;
  error:
  cson_value_free(payV);
  payV = NULL;
  ok:
  return payV;
}

/*
** Implementation of /json/timeline/wiki.
**
*/
static cson_value * json_timeline_wiki(unsigned int depth){
  /* This code is 95% the same as json_timeline_ci(), by the way. */
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_value * tmp = NULL;
  cson_value * listV = NULL;
  cson_array * list = NULL;
  int limit;
  int check = 0;
  Stmt q;
  Blob sql = empty_blob;
  if( !g.perm.Read || !g.perm.RdWiki ){
    g.json.resultCode = FSL_JSON_E_DENIED;
    return NULL;
  }
  limit = json_timeline_limit();
  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  json_timeline_temp_table();
  blob_append(&sql, "INSERT OR IGNORE INTO json_timeline ", -1);
  blob_append(&sql, json_timeline_query(), -1 );
  blob_append(&sql, "AND event.type IN('w') ", -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,"
              " uuid AS uuid,"
              " mtime AS timestamp,"
              " timestampString AS timestampString,"
              " comment AS comment, "
              " user AS user,"
              " eventType AS eventType"
#if 0
              /* can wiki pages have tags? */
              " tags AS tags," /*FIXME: split this into
                                 a JSON array*/
              " tagId AS tagId,"
#endif
              " FROM json_timeline"
              " ORDER BY mtime DESC",
              -1);
  db_prepare(&q,blob_buffer(&sql));
  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){
      json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED,
                 "Could not convert at least one timeline result row to JSON." );
      continue;
    }
    /* Split tags string field into JSON Array... */
    cson_array_append(list, rowV);
#if 0
    tagsStr = cson_value_get_string(cson_object_get(row,"tags"));
    if(tagsStr){
      cson_value * tags = json_string_split2( cson_string_cstr(tagsStr),
                                              ',', 0);
      if( tags ){
        if(0 != cson_object_set(row,"tags",tags)){
          cson_value_free(tags);
        }else{
          /*replaced/deleted old tags value, invalidating tagsStr*/;
          tagsStr = NULL;
        }
      }else{
        json_warn(FSL_JSON_W_STRING_TO_ARRAY_FAILED,
                  "Could not convert tags string to array.");
      }
    }
#endif
  }
  db_finalize(&q);
#undef SET
  goto ok;
  error:
  cson_value_free(payV);
  payV = NULL;
  ok:
  return payV;
}


/*
** Implements the /json/whoami page/command.
*/
static cson_value * json_page_whoami(unsigned int depth){
  cson_value * payload = NULL;
  cson_object * obj = NULL;