Fossil

Check-in [2162c86d]
Login

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

Overview
Comment:Implemented /json/wiki/save.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | json
Files: files | file ages | folders
SHA1:2162c86d9ec34458dd5db2a5a3f93b00f0914078
User & Date: stephan 2011-09-22 17:56:37
Context
2011-09-22
19:14
Implemented /json/wiki/create. check-in: 1df648ab user: stephan tags: json
17:56
Implemented /json/wiki/save. check-in: 2162c86d user: stephan tags: json
17:18
added src/Makefile so that (ctrl-x m) will DTRT. check-in: ce4f3103 user: stephan tags: json
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ajax/wiki-editor.html.

184
185
186
187
188
189
190

191
192
193
194
195
196
197
...
233
234
235
236
237
238
239


















240
241
242
243
244
245
246
...
263
264
265
266
267
268
269



270
271
272
273
274
275
276
...
282
283
284
285
286
287
288

289
290
291
292
293
294
295
    TheApp.cgi.onLogout = function(){
      TheApp.jqe.taResponse.val( "Logged out!" );
      TheApp.jqe.currentAuthToken.text("");
    };

    TheApp.showPage = function(name){
        function doShow(page){

            TheApp.jqe.spanPageName.text('('+page.name+')');
            TheApp.jqe.taPageContent.val(page.content);
        }
        var p = ('object' === typeof name) ? name : TheApp.pages[name];
        if('object' === typeof p) {
            doShow(p);
            return;
................................................................................
                if(resp.resultCode) return;
                else TheApp.updatePageList(resp.payload);
            }
        });
        return false /*for click handlers*/;
    }



















});

</script>

</head>

<body>
................................................................................
<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='wiki/list' onclick='TheApp.loadPageList()' />




<!--
<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")' />
<input type='button' value='save client data' onclick='TheApp.cgi.savePageClientData({"HelloWorld":[1,3,5]})' />
-->
................................................................................
        <th>Content <span id='spanPageName'></span></th>
    </tr>
    <tr>
        <td width='25%' valign='top'>
            <div id='pageListArea'></div>
        </td>
        <td width='75%' valign='top'>

            <textarea id='taPageContent' rows='20' cols='60'></textarea>
        </td>
    </tr>
    <tr>
        <th colspan='2'>Response <span id='timer'></span></th>
    </tr>
    <tr>







>







 







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







 







>
>
>







 







>







184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
...
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
...
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
    TheApp.cgi.onLogout = function(){
      TheApp.jqe.taResponse.val( "Logged out!" );
      TheApp.jqe.currentAuthToken.text("");
    };

    TheApp.showPage = function(name){
        function doShow(page){
            TheApp.currentPage = page;
            TheApp.jqe.spanPageName.text('('+page.name+')');
            TheApp.jqe.taPageContent.val(page.content);
        }
        var p = ('object' === typeof name) ? name : TheApp.pages[name];
        if('object' === typeof p) {
            doShow(p);
            return;
................................................................................
                if(resp.resultCode) return;
                else TheApp.updatePageList(resp.payload);
            }
        });
        return false /*for click handlers*/;
    }

    TheApp.savePage = function(p){
        p = p || TheApp.currentPage || TheApp.pages[TheApp.currentPage];
        if( 'object' !== typeof p ){
            p = TheApp.pages[p];
        }
        if('object' !== typeof p){
            alert("savePage() argument is not a page object or known page name.");
        }
        TheApp.pages[p.name] = p;
        p.content = TheApp.jqe.taPageContent.val();
        var req = {
            name:p.name,
            content:p.content
        };
        if(! confirm("Really save wiki page ["+p.name+"]?") ) return;
        TheApp.cgi.sendCommand('/json/wiki/save',req);
    };

});

</script>

</head>

<body>
................................................................................
<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='wiki/list' onclick='TheApp.loadPageList()' />
<!--
<input type='button' value='timeline/ci' onclick='TheApp.cgi.sendCommand("/json/timeline/ci")' />
-->

<!--
<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")' />
<input type='button' value='save client data' onclick='TheApp.cgi.savePageClientData({"HelloWorld":[1,3,5]})' />
-->
................................................................................
        <th>Content <span id='spanPageName'></span></th>
    </tr>
    <tr>
        <td width='25%' valign='top'>
            <div id='pageListArea'></div>
        </td>
        <td width='75%' valign='top'>
            <input type='button' value='Save' onclick='TheApp.savePage()' /><br/>
            <textarea id='taPageContent' rows='20' cols='60'></textarea>
        </td>
    </tr>
    <tr>
        <th colspan='2'>Response <span id='timer'></span></th>
    </tr>
    <tr>

Changes to src/json.c.

479
480
481
482
483
484
485












486
487
488
489
490
491
492
...
940
941
942
943
944
945
946














947
948
949
950
951
952
953
...
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
....
1476
1477
1478
1479
1480
1481
1482

1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
....
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592










































1593
1594
1595
1596
1597
1598
1599
....
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
          g.json.authToken = v;
        }
      }
    }
  }
  return g.json.authToken;
}













/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and
................................................................................
** repo is opened (will trigger a fatal error in db_xxx()).
*/
static cson_value * json_julian_to_timestamp(double j){
  return cson_value_new_integer((cson_int_t)
           db_int64(0,"SELECT strftime('%%s',%lf)",j)
                                );
}















/*
** Creates a new Fossil/JSON response envelope skeleton.  It is owned
** by the caller, who must eventually free it using cson_value_free(),
** or add it to a cson container to transfer ownership. Returns NULL
** on error.
**
................................................................................
    goto cleanup; \
  }while(0)

  tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID));
  SET("fossil");
 
  {/* timestamp */
    cson_int_t jsTime;
#if 1
    jsTime = (cson_int_t)time(0);
#elif 1
    /* Ge Weijers has pointed out that time(0) commonly returns
       UTC, but is not required to by The Standard.

       There is a mkfmtime() function in cgi.c but it requires
       a (tm *), and i don't have that without calling gmtime()
       or populating the tm myself (which is what i'm trying to
       have done for me!).
    */
    time_t const t = (time_t)time(0);
    struct tm gt = *gmtime(&t);
    gt.tm_isdst = -1;
    jsTime = (cson_int_t)mktime(&gt);
#else
    /* i'm not 100% sure that the above actually does what i expect,
       but we can't use the following because this function can be
       called in response to error handling if the db cannot be opened
       (or before that).
    */
    jsTime = (cson_int_t)db_int64(0, "SELECT strftime('%%s','now')");
#endif
    tmp = cson_value_new_integer(jsTime);
    SET(FossilJsonKeys.timestamp);
  }
  if( 0 != resultCode ){
    if( ! pMsg ) pMsg = json_err_str(resultCode);
    tmp = json_rc_string(resultCode);
    SET(FossilJsonKeys.resultCode);
  }
................................................................................
  return jv;
#undef SETBUF
}


static cson_value * json_wiki_list(unsigned int depth);
static cson_value * json_wiki_get(unsigned int depth);

/*
** Mapping of /json/wiki/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Wiki[] = {
{"get", json_wiki_get, 0},
{"list", json_wiki_list, 0},
{"save", json_page_nyi, 1},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};

/*
** A page/command dispatch helper for fossil_json_f() implementations.
** depth should be the depth parameter passed to the fossil_json_f().
................................................................................
    cson_object * pay = cson_value_get_object(payV);
    cson_object_set(pay,"name",json_new_string(zPageName));
    cson_object_set(pay,"version",json_new_string(pWiki->zBaseline))
      /*FIXME: pWiki->zBaseline is NULL. How to get the version number?*/
      ;
    cson_object_set(pay,"rid",cson_value_new_integer((cson_int_t)rid));
    cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser));
    cson_object_set(pay,"timestamp", json_julian_to_timestamp(pWiki->rDate));
    cson_object_set(pay,"contentLength",cson_value_new_integer((cson_int_t)len));
    cson_object_set(pay,"contentFormat",json_new_string(doParse?"html":"raw"));
    cson_object_set(pay,"content",cson_value_new_string(zBody,len));
    /*TODO: add 'T' (tag) fields*/
    /*TODO: add the 'A' card (file attachment) entries?*/
    manifest_destroy(pWiki);
    return payV;
  }
}











































/*
** Implementation of /json/wiki/list.
*/
static cson_value * json_wiki_list(unsigned int depth){
  cson_value * listV = NULL;
  cson_array * list = NULL;
................................................................................
{"logout",json_page_logout,1},
{"stat",json_page_stat,0},
{"tag", json_page_nyi,0},
{"ticket", json_page_nyi,0},
{"timeline", json_page_timeline,0},
{"user", json_page_nyi,0},
{"version",json_page_version,0},
{"whoami",json_page_whoami,1},
{"wiki",json_page_wiki,0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};


/*







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







 







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







 







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>






|







 







|









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







 







|







479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
...
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
....
1010
1011
1012
1013
1014
1015
1016
1017
























1018
1019
1020
1021
1022
1023
1024
....
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
....
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
....
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
          g.json.authToken = v;
        }
      }
    }
  }
  return g.json.authToken;
}

/*
** IFF json.reqPayload.o is not NULL then this returns
** cson_object_get(json.reqPayload.o,pKey), else it returns NULL.
**
** The returned value is owned by (or shared with) json.reqPayload.v.
*/
cson_value * json_req_payload_get(char const *pKey){
  return g.json.reqPayload.o
    ? cson_object_get(g.json.reqPayload.o,pKey)
    : NULL;
}

/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and
................................................................................
** repo is opened (will trigger a fatal error in db_xxx()).
*/
static cson_value * json_julian_to_timestamp(double j){
  return cson_value_new_integer((cson_int_t)
           db_int64(0,"SELECT strftime('%%s',%lf)",j)
                                );
}
/*
** Returns a timestamp value.
*/
static cson_int_t json_timestamp(){
  return (cson_int_t)time(0);
}
/*
** Returns a new JSON value (owned by the caller) representing
** a timestamp. If timeVal is < 0 then time(0) is used to fetch
** the time, else timeVal is used as-is
*/
static cson_value * json_new_timestamp(cson_int_t timeVal){
  return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal);
}

/*
** Creates a new Fossil/JSON response envelope skeleton.  It is owned
** by the caller, who must eventually free it using cson_value_free(),
** or add it to a cson container to transfer ownership. Returns NULL
** on error.
**
................................................................................
    goto cleanup; \
  }while(0)

  tmp = cson_value_new_string(MANIFEST_UUID,strlen(MANIFEST_UUID));
  SET("fossil");
 
  {/* timestamp */
    tmp = json_new_timestamp(-1);
























    SET(FossilJsonKeys.timestamp);
  }
  if( 0 != resultCode ){
    if( ! pMsg ) pMsg = json_err_str(resultCode);
    tmp = json_rc_string(resultCode);
    SET(FossilJsonKeys.resultCode);
  }
................................................................................
  return jv;
#undef SETBUF
}


static cson_value * json_wiki_list(unsigned int depth);
static cson_value * json_wiki_get(unsigned int depth);
static cson_value * json_wiki_save(unsigned int depth);
/*
** Mapping of /json/wiki/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Wiki[] = {
{"get", json_wiki_get, 0},
{"list", json_wiki_list, 0},
{"save", json_wiki_save, 1},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};

/*
** A page/command dispatch helper for fossil_json_f() implementations.
** depth should be the depth parameter passed to the fossil_json_f().
................................................................................
    cson_object * pay = cson_value_get_object(payV);
    cson_object_set(pay,"name",json_new_string(zPageName));
    cson_object_set(pay,"version",json_new_string(pWiki->zBaseline))
      /*FIXME: pWiki->zBaseline is NULL. How to get the version number?*/
      ;
    cson_object_set(pay,"rid",cson_value_new_integer((cson_int_t)rid));
    cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser));
    cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate));
    cson_object_set(pay,"contentLength",cson_value_new_integer((cson_int_t)len));
    cson_object_set(pay,"contentFormat",json_new_string(doParse?"html":"raw"));
    cson_object_set(pay,"content",cson_value_new_string(zBody,len));
    /*TODO: add 'T' (tag) fields*/
    /*TODO: add the 'A' card (file attachment) entries?*/
    manifest_destroy(pWiki);
    return payV;
  }
}

/*
** Implementation of /json/wiki/save.
*/
static cson_value * json_wiki_save(unsigned int depth){
  Blob content = empty_blob;
  cson_value * nameV;
  cson_value * contentV;
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_string const * jstr = NULL;
  char const * zContent;
  char const * zBody = NULL;
  char const * zPageName;
  if( !g.perm.WrWiki ){
    g.json.resultCode = FSL_JSON_E_DENIED;
    return NULL;
  }
  nameV = json_req_payload_get("name");
  contentV = nameV ? json_req_payload_get("content") : NULL;
  if(!nameV || !contentV){
    g.json.resultCode = FSL_JSON_E_MISSING_ARGS;
    return NULL;
  }
  if( !cson_value_is_string(nameV)
      || !cson_value_is_string(contentV)){
    g.json.resultCode = FSL_JSON_E_INVALID_ARGS;
    return NULL;
  }
  zPageName = cson_string_cstr(cson_value_get_string(nameV));
  jstr = cson_value_get_string(contentV);
  blob_append(&content, cson_string_cstr(jstr),(int)cson_string_length_bytes(jstr));
  wiki_cmd_commit(zPageName, 0, &content);
  blob_reset(&content);

  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  cson_object_set( pay, "name", nameV );
  cson_object_set( pay, FossilJsonKeys.timestamp,
                   json_new_timestamp(-1) );
  return payV;
}

/*
** Implementation of /json/wiki/list.
*/
static cson_value * json_wiki_list(unsigned int depth){
  cson_value * listV = NULL;
  cson_array * list = NULL;
................................................................................
{"logout",json_page_logout,1},
{"stat",json_page_stat,0},
{"tag", json_page_nyi,0},
{"ticket", json_page_nyi,0},
{"timeline", json_page_timeline,0},
{"user", json_page_nyi,0},
{"version",json_page_version,0},
{"whoami",json_page_whoami,1/*FIXME: work in CLI mode*/},
{"wiki",json_page_wiki,0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};


/*

Changes to src/wiki.c.

799
800
801
802
803
804
805

806
807
808

809
810
811
812
813
814
815
  rid = db_int(0,
     "SELECT x.rid FROM tag t, tagxref x"
     " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
     " ORDER BY x.mtime DESC LIMIT 1",
     zPageName
  );
  if( rid==0 && !isNew ){

    fossil_fatal("no such wiki page: %s", zPageName);
  }
  if( rid!=0 && isNew ){

    fossil_fatal("wiki page %s already exists", zPageName);
  }

  blob_zero(&wiki);
  zDate = date_in_standard_format("now");
  blob_appendf(&wiki, "D %s\n", zDate);
  free(zDate);







>



>







799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
  rid = db_int(0,
     "SELECT x.rid FROM tag t, tagxref x"
     " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
     " ORDER BY x.mtime DESC LIMIT 1",
     zPageName
  );
  if( rid==0 && !isNew ){
    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
    fossil_fatal("no such wiki page: %s", zPageName);
  }
  if( rid!=0 && isNew ){
    g.json.resultCode = FSL_JSON_E_DENIED/*need a better code for this*/;
    fossil_fatal("wiki page %s already exists", zPageName);
  }

  blob_zero(&wiki);
  zDate = date_in_standard_format("now");
  blob_appendf(&wiki, "D %s\n", zDate);
  free(zDate);