Fossil

Check-in [d32155d0]
Login

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

Overview
Comment:eliminate some end-of-line spaces
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:d32155d0642988c4ec14e5e4cdda8c5e89a8d1c0
User & Date: jan.nijtmans 2016-08-23 12:13:25
Context
2016-08-23
12:14
update change log check-in: 299614d1 user: jan.nijtmans tags: trunk
12:13
eliminate some end-of-line spaces check-in: d32155d0 user: jan.nijtmans tags: trunk
11:58
Add a link to the /uvlist screen from the /setup menu. Add the byage=1 query parameter to /uvlist. check-in: ab4c1e29 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ajax/js/fossil-ajaj.js.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
...
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
...
224
225
226
227
228
229
230
231
232
233
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

    License: Public Domain
*/

/**
    Constructor for a new Fossil AJAJ client. ajajOpt may be an optional
    object suitable for passing to the WhAjaj.Connector() constructor.
    
    On returning, this.ajaj is-a WhAjaj.Connector instance which can 
    be used to send requests to the back-end (though the convenience 
    functions of this class are the preferred way to do it). Clients 
    are encouraged to use FossilAjaj.sendCommand() (and friends) instead 
    of the underlying WhAjaj.Connector API, since this class' API 
    contains Fossil-specific request-calling handling (e.g. of authentication
    info) whereas WhAjaj is more generic.
*/
function FossilAjaj(ajajOpt)
{
    this.ajaj = new WhAjaj.Connector(ajajOpt);
    return this;
................................................................................
};

/**
    Sends a command to the fossil back-end. Command should be the
    path part of the URL, e.g. /json/stat, payload is a request-specific
    value type (may often be null/undefined). ajajOpt is an optional object
    holding WhAjaj.sendRequest()-compatible options.
    
    This function constructs a Fossil/JSON request envelope based
    on the given arguments and adds this.auth.authToken and a requestId
    to it.
*/
FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) {
    var req;
    ajajOpt = ajajOpt || {};
................................................................................
    if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command;
    this.ajaj.sendRequest(req,ajajOpt);
};

/**
    Sends a login request to the back-end.

    ajajOpt is an optional configuration object suitable for passing 
    to sendCommand().

    After the response returns, this.auth will be
    set to the response payload.
    
    If name === 'anonymous' (the default if none is passed in) then this
    function ignores the pw argument and must make two requests - the first
    one gets the captcha code and the second one submits it.
    ajajOpt.onResponse() (if set) is only called for the actual login
    response (the 2nd one), as opposed to being called for both requests.
    However, this.ajaj.callbacks.onResponse() _is_ called for both (because
    it happens at a lower level).
    
    If this object has an onLogin() function it is called (with
    no arguments) before the onResponse() handler of the login is called
    (that is the 2nd request for anonymous logins) and any exceptions
    it throws are ignored.

*/
FossilAjaj.prototype.login = function(name,pw,ajajOpt) {
................................................................................
    }
    else doLogin();
};

/**
    Logs out of fossil, invaliding this login token.

    ajajOpt is an optional configuration object suitable for passing 
    to sendCommand().
    
    If this object has an onLogout() function it is called (with
    no arguments) before the onResponse() handler is called.
    IFF the response succeeds then this.auth is unset.
*/
FossilAjaj.prototype.logout = function(ajajOpt) {
    var self = this;
    ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
................................................................................
    };
    this.sendCommand('/json/logout', undefined, ajajOpt );
};

/**
    Sends a HAI request to the server. /json/HAI is an alias /json/version.

    ajajOpt is an optional configuration object suitable for passing 
    to sendCommand().
*/
FossilAjaj.prototype.HAI = function(ajajOpt) {
    this.sendCommand('/json/HAI', undefined, ajajOpt);
};


................................................................................
    mode, feeds it the request envelope, and returns the response envelope
    via the same mechanisms defined for the HTTP-based implementations.

    The interface is otherwise compatible with the "normal"
    FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and
    not back-end agnostic like the WhAjaj.sendImpl() interface intends).

    
*/
FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){
    var self = this;
    request = request || {};
    if(!args.fossilBinary){
        throw new Error("fossilBinary is not set on AJAX options!");
    }
    var url = args.url.split('?')[0].split(/\/+/);
    if(url.length>1){
        // 3x shift(): protocol, host, 'json' part of path
        request.command = (url.shift(),url.shift(),url.shift(), url.join('/'));
    }
    delete args.url;
    //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request));    
    var json;
    try{
        var pargs = [args.fossilBinary, 'json', '--json-input', '-'];
        var p = java.lang.Runtime.getRuntime().exec(pargs);
        var outs = p.getOutputStream();
        var osr = new java.io.OutputStreamWriter(outs);
        var osb = new java.io.BufferedWriter(osr);
        
        json = JSON.stringify(request);
        osb.write(json,0, json.length);
        osb.close();
        var ins = p.getInputStream();
        var isr = new java.io.InputStreamReader(ins);
        var br = new java.io.BufferedReader(isr);
        var line;







|
|
|
|
|
|







 







|







 







|




|







|







 







|

|







 







|







 







|













|







|







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
...
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
...
224
225
226
227
228
229
230
231
232
233
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

    License: Public Domain
*/

/**
    Constructor for a new Fossil AJAJ client. ajajOpt may be an optional
    object suitable for passing to the WhAjaj.Connector() constructor.

    On returning, this.ajaj is-a WhAjaj.Connector instance which can
    be used to send requests to the back-end (though the convenience
    functions of this class are the preferred way to do it). Clients
    are encouraged to use FossilAjaj.sendCommand() (and friends) instead
    of the underlying WhAjaj.Connector API, since this class' API
    contains Fossil-specific request-calling handling (e.g. of authentication
    info) whereas WhAjaj is more generic.
*/
function FossilAjaj(ajajOpt)
{
    this.ajaj = new WhAjaj.Connector(ajajOpt);
    return this;
................................................................................
};

/**
    Sends a command to the fossil back-end. Command should be the
    path part of the URL, e.g. /json/stat, payload is a request-specific
    value type (may often be null/undefined). ajajOpt is an optional object
    holding WhAjaj.sendRequest()-compatible options.

    This function constructs a Fossil/JSON request envelope based
    on the given arguments and adds this.auth.authToken and a requestId
    to it.
*/
FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) {
    var req;
    ajajOpt = ajajOpt || {};
................................................................................
    if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command;
    this.ajaj.sendRequest(req,ajajOpt);
};

/**
    Sends a login request to the back-end.

    ajajOpt is an optional configuration object suitable for passing
    to sendCommand().

    After the response returns, this.auth will be
    set to the response payload.

    If name === 'anonymous' (the default if none is passed in) then this
    function ignores the pw argument and must make two requests - the first
    one gets the captcha code and the second one submits it.
    ajajOpt.onResponse() (if set) is only called for the actual login
    response (the 2nd one), as opposed to being called for both requests.
    However, this.ajaj.callbacks.onResponse() _is_ called for both (because
    it happens at a lower level).

    If this object has an onLogin() function it is called (with
    no arguments) before the onResponse() handler of the login is called
    (that is the 2nd request for anonymous logins) and any exceptions
    it throws are ignored.

*/
FossilAjaj.prototype.login = function(name,pw,ajajOpt) {
................................................................................
    }
    else doLogin();
};

/**
    Logs out of fossil, invaliding this login token.

    ajajOpt is an optional configuration object suitable for passing
    to sendCommand().

    If this object has an onLogout() function it is called (with
    no arguments) before the onResponse() handler is called.
    IFF the response succeeds then this.auth is unset.
*/
FossilAjaj.prototype.logout = function(ajajOpt) {
    var self = this;
    ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} );
................................................................................
    };
    this.sendCommand('/json/logout', undefined, ajajOpt );
};

/**
    Sends a HAI request to the server. /json/HAI is an alias /json/version.

    ajajOpt is an optional configuration object suitable for passing
    to sendCommand().
*/
FossilAjaj.prototype.HAI = function(ajajOpt) {
    this.sendCommand('/json/HAI', undefined, ajajOpt);
};


................................................................................
    mode, feeds it the request envelope, and returns the response envelope
    via the same mechanisms defined for the HTTP-based implementations.

    The interface is otherwise compatible with the "normal"
    FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and
    not back-end agnostic like the WhAjaj.sendImpl() interface intends).


*/
FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){
    var self = this;
    request = request || {};
    if(!args.fossilBinary){
        throw new Error("fossilBinary is not set on AJAX options!");
    }
    var url = args.url.split('?')[0].split(/\/+/);
    if(url.length>1){
        // 3x shift(): protocol, host, 'json' part of path
        request.command = (url.shift(),url.shift(),url.shift(), url.join('/'));
    }
    delete args.url;
    //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request));
    var json;
    try{
        var pargs = [args.fossilBinary, 'json', '--json-input', '-'];
        var p = java.lang.Runtime.getRuntime().exec(pargs);
        var outs = p.getOutputStream();
        var osr = new java.io.OutputStreamWriter(outs);
        var osb = new java.io.BufferedWriter(osr);

        json = JSON.stringify(request);
        osb.write(json,0, json.length);
        osb.close();
        var ins = p.getInputStream();
        var isr = new java.io.InputStreamReader(ins);
        var br = new java.io.BufferedReader(isr);
        var line;

Changes to ajax/js/whajaj.js.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
...
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
...
228
229
230
231
232
233
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
...
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
...
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
...
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
...
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
...
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
...
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
...
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
...
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
....
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
....
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
....
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194

    All functionality is part of a class named WhAjaj, and that class
    acts as namespace for this framework.

    Author: Stephan Beal (http://wanderinghorse.net/home/stephan/)

    License: Public Domain
    
    This framework is directly derived from code originally found in 
    http://code.google.com/p/jsonmessage, and later in 
    http://whiki.wanderinghorse.net, where it contained quite a bit 
    of application-specific logic. It was eventually (the 3rd time i 
    needed it) split off into its own library to simplify inclusion 
    into my many mini-projects.
*/


/**
    The WhAjaj function is primarily a namespace, and not intended 
    to called or instantiated via the 'new' operator.
*/
function WhAjaj()
{
}

/** Returns a millisecond Unix Epoch timestamp. */
................................................................................
WhAjaj.msTimestamp = function()
{
    return (new Date()).getTime();
};

/** Returns a Unix Epoch timestamp (in seconds) in integer format.

    Reminder to self: (1.1 %1.2) evaluates to a floating-point value 
    in JS, and thus this implementation is less than optimal.
*/
WhAjaj.unixTimestamp = function()
{
    var ts = (new Date()).getTime();
    return parseInt( ""+((ts / 1000) % ts) );
};
................................................................................
    )
    ;
};

/**
    Parses window.location.search-style string into an object
    containing key/value pairs of URL arguments (already urldecoded).
    
    If the str argument is not passed (arguments.length==0) then 
    window.location.search.substring(1) is used by default. If 
    neither str is passed in nor window exists then false is returned.

    On success it returns an Object containing the key/value pairs
    parsed from the string. Keys which have no value are treated
    has having the boolean true value.
    
    FIXME: for keys in the form "name[]", build an array of results,
    like PHP does.
 
*/
WhAjaj.processUrlArgs = function(str) {
    if( 0 === arguments.length ) {
        if( ('undefined' === typeof window) ||
            !window.location ||
            !window.location.search )  return false;
        else str = (''+window.location.search).substring(1);
................................................................................
};

/**
    A simple wrapper around JSON.stringify(), using my own personal
    preferred values for the 2nd and 3rd parameters. To globally
    set its indentation level, assign WhAjaj.stringify.indent to
    an integer value (0 for no intendation).
    
    This function is intended only for human-readable output, not
    generic over-the-wire JSON output (where JSON.stringify(val) will
    produce smaller results).
*/
WhAjaj.stringify = function(val) {
    if( ! arguments.callee.indent ) arguments.callee.indent = 4;
    return JSON.stringify(val,0,arguments.callee.indent);
};

/**
    Each instance of this class holds state information for making 
    AJAJ requests to a back-end system. While clients may use one 
    "requester" object per connection attempt, for connections to the
    same back-end, using an instance configured for that back-end 
    can simplify usage. This class is designed so that the actual 
    connection-related details (i.e. _how_ it connects to the 
    back-end) may be re-implemented to use a client's preferred 
    connection mechanism (e.g. jQuery).
    
    The optional opt paramater may be an object with any (or all) of 
    the properties documented for WhAjaj.Connector.options.ajax. 
    Properties set here (or later via modification of the "options" 
    property of this object) will be used in calls to 
    WhAjaj.Connector.sendRequest(), and these override (normally) any
    options set in WhAjaj.Connector.options.ajax. Note that 
    WhAjaj.Connector.sendRequest() _also_ takes an options object, 
    and ones passed there will override, for purposes of that one 
    request, any options passed in here or defined in 
    WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax
    and WhAjaj.Connector.prototype.sendRequest() for more details
    about the precedence of options.
    
    Sample usage:
    
    @code
    // Set up common connection-level options:
    var cgi = new WhAjaj.Connector({
        url: '/cgi-bin/my.cgi',
        timeout:10000,
        onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); },
        onError(req,opt) {
................................................................................
        onResponse(resp,req){ alert(WhAjaj.stringify(resp)); }
    });
    @endcode

    For common request types, clients can add functions to this
    object which act as wrappers for backend-specific functionality. As
    a simple example:
    
    @code
    cgi.login = function(name,pw,ajajOpt) {
        this.sendRequest(
            {command:"json/login",
              name:name,
              password:pw
            }, ajajOpt );
    };
    @endcode
    
    TODOs:
    
    - Caching of page-load requests, with a configurable lifetime.
    
    - Use-cases like the above login() function are a tiny bit
    problematic to implement when each request has a different URL
    path (i know this from the whiki and fossil implementations).
    This is partly a side-effect of design descisions made back in
    the very first days of this code's life. i need to go through
    and see where i can bend those conventions a bit (where it won't
    break my other apps unduly).
................................................................................
WhAjaj.Connector.options = {
    /**
        A (meaningless) prefix to apply to WhAjaj.Connector-generated
        request IDs.
    */
    requestIdPrefix:'WhAjaj.Connector-',
    /**
        Default options for WhAjaj.Connector.sendRequest() connection 
        parameters. This object holds only connection-related 
        options and callbacks (all optional), and not options 
        related to the required JSON structure of any given request. 
        i.e. the page name used in a get-page request are not set 
        here but are specified as part of the request object.

        These connection options are a "normalized form" of options 
        often found in various AJAX libraries like jQuery, 
        Prototype, dojo, etc. This approach allows us to swap out 
        the real connection-related parts by writing a simple proxy 
        which transforms our "normalized" form to the 
        backend-specific form. For examples, see the various 
        implementations stored in WhAjaj.Connector.sendImpls.

        The following callback options are, in practice, almost 
        always set globally to some app-wide defaults:

        - onError() to report errors using a common mechanism.
        - beforeSend() to start a visual activity notification
        - afterSend() to disable the visual activity notification

        However, be aware that if any given WhAjaj.Connector instance is 
        given its own before/afterSend callback then those will 
        override these. Mixing shared/global and per-instance
        callbacks can potentially lead to confusing results if, e.g.,
        the beforeSend() and afterSend() functions have side-effects
        but are not used with their proper before/after partner.
        
        TODO: rename this to 'ajaj' (the name is historical). The 
        problem with renaming it is is that the word 'ajax' is 
        pretty prevelant in the source tree, so i can't globally 
        swap it out.
    */
    ajax: {
        /**
            URL of the back-end server/CGI.
        */
        url: '/some/path',

        /**
            Connection method. Some connection-related functions might
            override any client-defined setting.
            
            Must be one of 'GET' or 'POST'. For custom connection 
            implementation, it may optionally be some 
            implementation-specified value.

            Normally the API can derive this value automatically - if the
            request uses JSON data it is POSTed, else it is GETted.
        */
        method:'GET',

        /**
            A hint whether to run the operation asynchronously or 
            not. Not all concrete WhAjaj.Connector.sendImpl() 
            implementations can support this. Interestingly, at 
            least one popular AJAX toolkit does not document 
            supporting _synchronous_ AJAX operations. All common 
            browser-side implementations support async operation, but 
            non-browser implementations might not.
        */
        asynchronous:true,

        /**
            A HTTP authentication login name for the AJAX 
            connection. Not all concrete WhAjaj.Connector.sendImpl() 
            implementations can support this.
        */
        loginName:undefined,

        /**
            An HTTP authentication login password for the AJAJ 
            connection. Not all concrete WhAjaj.Connector.sendImpl() 
            implementations can support this.
        */
        loginPassword:undefined,

        /**
            A connection timeout, in milliseconds, for establishing 
            an AJAJ connection. Not all concrete 
            WhAjaj.Connector.sendImpl() implementations can support this.
        */
        timeout:10000,

        /**
            If an AJAJ request receives JSON data from the back-end,
            that data is passed as a plain Object as the response
................................................................................
            ignore it (only those which need a way to map specific
            requests to responses will need it). The 3rd parameter
            is the same as the 'this' object for the context of the
            callback, but is provided because the instance-level
            callbacks (set in (WhAjaj.Connector instance).callbacks,
            require it in some cases (because their 'this' is
            different!).
            
            Note that the response might contain error information
            which comes from the back-end. The difference between
            this error info and the info passed to the onError()
            callback is that this data indicates an
            application-level error, whereas onError() is used to
            report connection-level problems or when the backend
            produces non-JSON data (which, when not in jsonp mode,
            is unexpected and is as fatal to the request as a
            connection error).
        */
        onResponse: function(response, request, opt){},

        /**
            If an AJAX request fails to establish a connection or it 
            receives non-JSON data from the back-end, this function 
            is called (e.g. timeout error or host name not 
            resolvable). It is passed the originating request and the
            "normalized" connection parameters used for that 
            request. The connectOpt object "should" (or "might") 
            have an "errorMessage" property which describes the 
            nature of the problem.
            
            Clients will almost always want to replace the default 
            implementation with something which integrates into 
            their application.
        */
        onError: function(request, connectOpt)
        {
            alert('AJAJ request failed:\n'
                +'Connection information:\n'
                +JSON.stringify(connectOpt,0,4)
            );
        },

        /**
            Called before each connection attempt is made. Clients 
            can use this to, e.g.,  enable a visual "network activity
            notification" for the user. It is passed the original 
            request object and the normalized connection parameters 
            for the request. If this function changes opt, those 
            changes _are_ applied to the subsequent request. If this 
            function throws, neither the onError() nor afterSend() 
            callbacks are triggered and WhAjaj.Connector.sendImpl() 
            propagates the exception back to the caller.
        */
        beforeSend: function(request,opt){},

        /**
            Called after an AJAJ connection attempt completes, 
            regardless of success or failure. Passed the same 
            parameters as beforeSend() (see that function for 
            details).
            
            Here's an example of setting up a visual notification on 
            ajax operations using jQuery (but it's also easy to do 
            without jQuery as well):
            
            @code
            function startAjaxNotif(req,opt) {
                var me = arguments.callee;
                var c = ++me.ajaxCount;
                me.element.text( c + " pending AJAX operation(s)..." );
                if( 1 == c ) me.element.stop().fadeIn();
            }
            startAjaxNotif.ajaxCount = 0.
            startAjaxNotif.element = jQuery('#whikiAjaxNotification');
            
            function endAjaxNotif() {
                var c = --startAjaxNotif.ajaxCount;
                startAjaxNotif.element.text( c+" pending AJAX operation(s)..." );
                if( 0 == c ) startAjaxNotif.element.stop().fadeOut();
            }
            @endcode

            Set the beforeSend/afterSend properties to those 
            functions to enable the notifications by default.            
        */
        afterSend: function(request,opt){},

        /**
            If jsonp is a string then the WhAjaj-internal response
            handling code ASSUMES that the response contains a JSONP-style
            construct and eval()s it after afterSend() but before onResponse().
            In this case, onResponse() will get a string value for the response
            instead of a response object parsed from JSON. 
        */
        jsonp:undefined,
        /**
            Don't use yet. Planned future option.
        */
        propagateExceptions:false
    }
................................................................................
    else v = this.options[key];
    if( undefined !== v ) return v;
    else v = WhAjaj.Connector.options.ajax[key];
    return v;
};

/**
    Returns a unique string on each call containing a generic 
    reandom request identifier string. This is not used by the core 
    API but can be used by client code to generate unique IDs for 
    each request (if needed).

    The exact format is unspecified and may change in the future.

    Request IDs can be used by clients to "match up" responses to 
    specific requests if needed. In practice, however, they are 
    seldom, if ever, needed. When passing several concurrent 
    requests through the same response callback, it might be useful 
    for some clients to be able to distinguish, possibly re-routing 
    them through other handlers based on the originating request type.
    
    If this.options.requestIdPrefix or 
    WhAjaj.Connector.options.requestIdPrefix is set then that text
    is prefixed to the returned string.
*/
WhAjaj.Connector.prototype.generateRequestId = function()
{
    if( undefined === arguments.callee.sequence )
    {
................................................................................
        if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */;
        this.options[k] = opt[k];
    }
    return this.options;
};

/**
    An internal helper object which holds several functions intended 
    to simplify the creation of concrete communication channel 
    implementations for WhAjaj.Connector.sendImpl(). These operations
    take care of some of the more error-prone parts of ensuring that
    onResponse(), onError(), etc. callbacks are called consistently 
    using the same rules.
*/
WhAjaj.Connector.sendHelper = {
    /**
        opt is assumed to be a normalized set of 
        WhAjaj.Connector.sendRequest() options. This function 
        creates a url by concatenating opt.url and some form of 
        opt.urlParam.
        
        If opt.urlParam is an object or string then it is appended 
        to the url. An object is assumed to be a one-dimensional set 
        of simple (urlencodable) key/value pairs, and not larger 
        data structures. A string value is assumed to be a 
        well-formed, urlencoded set of key/value pairs separated by 
        '&' characters.
        
        The new/normalized URL is returned (opt is not modified). If 
        opt.urlParam is not set then opt.url is returned (or an 
        empty string if opt.url is itself a false value).
        
        TODO: if opt is-a Object and any key points to an array, 
        build up a list of keys in the form "keyname[]". We could 
        arguably encode sub-objects like "keyname[subkey]=...", but 
        i don't know if that's conventions-compatible with other 
        frameworks.
    */
    normalizeURL: function(opt) {
        var u = opt.url || '';
        if( opt.urlParam ) {
            var addQ = (u.indexOf('?') >= 0) ? false : true;
            var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false);
................................................................................
                tail = opt.urlParam;
            }
            u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail;
        }
        return u;
    },
    /**
        Should be called by WhAjaj.Connector.sendImpl() 
        implementations after a response has come back. This 
        function takes care of most of ensuring that framework-level 
        conventions involving WhAjaj.Connector.options.ajax 
        properties are followed.
        
        The request argument must be the original request passed to 
        the sendImpl() function. It may legally be null for GET requests.
        
        The opt object should be the normalized AJAX options used 
        for the connection.
        
        The resp argument may be either a plain Object or a string 
        (in which case it is assumed to be JSON).

        The 'this' object for this call MUST be a WhAjaj.Connector
        instance in order for callback processing to work properly.
        
        This function takes care of the following:
        
        - Calling opt.afterSend()
        
        - If resp is a string, de-JSON-izing it to an object.
        
        - Calling opt.onResponse()
        
        - Calling opt.onError() in several common (potential) error 
        cases.

        - If resp is-a String and opt.jsonp then resp is assumed to be
        a JSONP-form construct and is eval()d BEFORE opt.onResponse()
        is called. It is arguable to eval() it first, but the logic
        integrates better with the non-jsonp handler.

        The sendImpl() should return immediately after calling this.
        
        The sendImpl() must call only one of onSendSuccess() or 
        onSendError(). It must call one of them or it must implement 
        its own response/error handling, which is not recommended 
        because getting the documented semantics of the 
        onError/onResponse/afterSend handling correct can be tedious.
    */
    onSendSuccess:function(request,resp,opt) {
        var cb = this.callbacks || {};
        if( WhAjaj.isFunction(cb.afterSend) ) {
            try {cb.afterSend( request, opt );}
            catch(e){}
................................................................................
    },
   /**
        Should be called by sendImpl() implementations after a response
        has failed to connect (e.g. could not resolve host or timeout
        reached). This function takes care of most of ensuring that
        framework-level conventions involving WhAjaj.Connector.options.ajax
        properties are followed.
        
        The request argument must be the original request passed to 
        the sendImpl() function. It may legally be null for GET 
        requests.

        The 'this' object for this call MUST be a WhAjaj.Connector
        instance in order for callback processing to work properly.
        
        The opt object should be the normalized AJAX options used 
        for the connection. By convention, the caller of this 
        function "should" set opt.errorMessage to contain a 
        human-readable description of the error.
        
        The sendImpl() should return immediately after calling this. The
        return value from this function is unspecified.
    */
    onSendError: function(request,opt) {
        var cb = this.callbacks || {};
        if( WhAjaj.isFunction(cb.afterSend) ) {
            try {cb.afterSend( request, opt );}
................................................................................
            try {opt.onError( request, opt );}
            catch(e) {/*ignore*/}
        }
    }
};

/**
    WhAjaj.Connector.sendImpls holds several concrete 
    implementations of WhAjaj.Connector.prototype.sendImpl(). To use 
    a specific implementation by default assign 
    WhAjaj.Connector.prototype.sendImpl to one of these functions.
    
    The functions defined here require that the 'this' object be-a
    WhAjaj.Connector instance.
    
    Historical notes:

    a) We once had an implementation based on Prototype, but that 
    library just pisses me off (they change base-most types' 
    prototypes, introducing side-effects in client code which 
    doesn't even use Prototype). The Prototype version at the time 
    had a serious toJSON() bug which caused empty arrays to 
    serialize as the string "[]", which broke a bunch of my code. 
    (That has been fixed in the mean time, but i don't use 
    Prototype.)
    
    b) We once had an implementation for the dojo library, 
    
    If/when the time comes to add Prototype/dojo support, we simply
    need to port:
    
    http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js
    
    (search that file for "dojo" and "Prototype") to this tree. That 
    code is this code's generic grandfather and they are still very 
    similar, so a port is trivial.    
    
*/
WhAjaj.Connector.sendImpls = {
    /**
        This is a concrete implementation of 
        WhAjaj.Connector.prototype.sendImpl() which uses the 
        environment's native XMLHttpRequest class to send whiki 
        requests and fetch the responses.

        The only argument must be a connection properties object, as 
        constructed by WhAjaj.Connector.normalizeAjaxParameters().

        If window.firebug is set then window.firebug.watchXHR() is 
        called to enable monitoring of the XMLHttpRequest object.

        This implementation honors the loginName and loginPassword 
        connection parameters.

        Returns the XMLHttpRequest object.

        This implementation requires that the 'this' object be-a 
        WhAjaj.Connector.
        
        This implementation uses setTimeout() to implement the 
        timeout support, and thus the JS engine must provide that 
        functionality.
    */
    XMLHttpRequest: function(request, args)
    {
        var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request;
        var xhr = new XMLHttpRequest();
        var startTime = (new Date()).getTime();
................................................................................
        {
            args.errorMessage = e.toString();
            WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
            return undefined;
        }
    }/*XMLHttpRequest()*/,
    /**
        This is a concrete implementation of 
        WhAjaj.Connector.prototype.sendImpl() which uses the jQuery 
        AJAX API to send requests and fetch the responses.

        The first argument may be either null/false, an Object 
        containing toJSON-able data to post to the back-end, or such an
        object in JSON string form.

        The second argument must be a connection properties object, as 
        constructed by WhAjaj.Connector.normalizeAjaxParameters().

        If window.firebug is set then window.firebug.watchXHR() is 
        called to enable monitoring of the XMLHttpRequest object.

        This implementation honors the loginName and loginPassword 
        connection parameters.

        Returns the XMLHttpRequest object.

        This implementation requires that the 'this' object be-a 
        WhAjaj.Connector.
    */
    jQuery:function(request,args)
    {
        var data = request || undefined;
        var whself = this;
        if( data ) {
................................................................................
        {
            args.errorMessage = e.toString();
            WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
            return undefined;
        }
    }/*jQuery()*/,
    /**
        This is a concrete implementation of 
        WhAjaj.Connector.prototype.sendImpl() which uses the rhino 
        Java API to send requests and fetch the responses.

        Limitations vis-a-vis the interface:

        - timeouts are not supported.

        - asynchronous mode is not supported because implementing it
................................................................................
        json = json.join('');
        //print("READ IN JSON: "+json);
        WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
    }/*rhino*/
};

/**
    An internal function which takes an object containing properties 
    for a WhAjaj.Connector network request. This function creates a new 
    object containing a superset of the properties from:

    a) opt
    b) this.options
    c) WhAjaj.Connector.options.ajax

    in that order, using the first one it finds.

    All non-function properties are _deeply_ copied via JSON cloning 
    in order to prevent accidental "cross-request pollenation" (been 
    there, done that). Functions cannot be cloned and are simply 
    copied by reference.
    
    This function throws if JSON-copying one of the options fails
    (e.g. due to cyclic data structures).
    
    Reminder to self: this function does not "normalize" opt.urlParam
    by encoding it into opt.url, mainly for historical reasons, but
    also because that behaviour was specifically undesirable in this
    code's genetic father.
*/
WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt)
{
................................................................................
    cp( this.options );
    cp( WhAjaj.Connector.options.ajax );
    // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc);
    return rc;
};

/**
    This is the generic interface for making calls to a back-end 
    JSON-producing request handler. It is a simple wrapper around 
    WhAjaj.Connector.prototype.sendImpl(), which just normalizes the 
    connection options for sendImpl() and makes sure that 
    opt.beforeSend() is (possibly) called.
    
    The request parameter must either be false/null/empty or a 
    fully-populated JSON-able request object (which will be sent as 
    unencoded application/json text), depending on the type of 
    request being made. It is never semantically legal (in this API) 
    for request to be a string/number/true/array value. As a rule, 
    only POST requests use the request data. GET requests should 
    encode their data in opt.url or opt.urlParam (see below).
    
    opt must contain the network-related parameters for the request. 
    Paramters _not_ set in opt are pulled from this.options or 
    WhAjaj.Connector.options.ajax (in that order, using the first 
    value it finds). Thus the set of connection-level options used 
    for the request are a superset of those various sources.
    
    The "normalized" (or "superimposed") opt object's URL may be 
    modified before the request is sent, as follows:

    if opt.urlParam is a string then it is assumed to be properly 
    URL-encoded parameters and is appended to the opt.url. If it is 
    an Object then it is assumed to be a one-dimensional set of 
    key/value pairs with simple values (numbers, strings, booleans, 
    null, and NOT objects/arrays). The keys/values are URL-encoded 
    and appended to the URL.

    The beforeSend() callback (see below) can modify the options
    object before the request attempt is made.
   
    The callbacks in the normalized opt object will be triggered as
    follows (if they are set to Function values):
    
    - beforeSend(request,opt) will be called before any network 
    processing starts. If beforeSend() throws then no other 
    callbacks are triggered and this function propagates the 
    exception. This function is passed normalized connection options
    as its second parameter, and changes this function makes to that
    object _will_ be used for the pending connection attempt.
    
    - onError(request,opt) will be called if a connection to the 
    back-end cannot be established. It will be passed the original 
    request object (which might be null, depending on the request 
    type) and the normalized options object. In the error case, the 
    opt object passed to onError() "should" have a property called 
    "errorMessage" which contains a description of the problem.
    
    - onError(request,opt) will also be called if connection 
    succeeds but the response is not JSON data.
    
    - onResponse(response,request) will be called if the response 
    returns JSON data. That data might hold an error response code - 
    clients need to check for that. It is passed the response object 
    (a plain object) and the original request object.
    
    - afterSend(request,opt) will be called directly after the
    AJAX request is finished, before onError() or onResonse() are
    called. Possible TODO: we explicitly do NOT pass the response to
    this function in order to keep the line between the responsibilities
    of the various callback clear (otherwise this could be used the same
    as onResponse()). In practice it would sometimes be useful have the
    response passed to this function, mainly for logging/debugging
................................................................................
{
    if( !WhAjaj.isFunction(this.sendImpl) )
    {
        throw new Error("This object has no sendImpl() member function! I don't know how to send the request!");
    }
    var ex = false;
    var av = Array.prototype.slice.apply( arguments, [0] );
    
    /**
        FIXME: how to handle the error, vis-a-vis- the callbacks, if 
        normalizeAjaxParameters() throws? It can throw if 
        (de)JSON-izing fails.
    */
    var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} );
    norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm);
    if( ! request ) norm.method = 'GET';
    var cb = this.callbacks || {};
    if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) {







|
|
|
|
|
|





|







 







|







 







|
|
|





|


|







 







|










|
|

|
|
|
|

|
|
|
|
|

|
|
|
|



|

|







 







|









|

|

|







 







|
|
|
|
|


|
|
|
|
|
|


|






|
|




|
|
|
|











|
|
|








|
|
|
|
|
|





|
|





|
|





|
|







 







|













|
|
|

|
|
|

|
|
|











|

|
|
|
|
|
|





|
|
|

|
|
|

|









|







|
|








|







 







|
|
|




|
|
|
|
|

|
|







 







|
|


|




|
|
|

|
|
|
|
|
|

|
|
|

|
|
|
|
|







 







|
|
|
|

|
|

|
|

|
|




|

|

|

|

|
|








|
|
|
|
|







 







|
|
|




|
|
|
|

|







 







|
|
|

|


|


|
|
|
|
|
|
|

|
|
|


|

|
|
|
|
|



|
|
|


|


|


|




|

|
|
|







 







|
|


|



|


|


|




|







 







|
|







 







|
|








|
|
|

|


|







 







|
|
|
|

|
|
|
|
|
|
|

|
|
|
|
|

|
|


|
|
|
|
|




|


|
|
|
|



|
|
|
|
|
|

|
|

|
|
|
|

|







 







|

|
|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
...
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
...
228
229
230
231
232
233
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
...
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
...
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
...
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
...
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
...
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
...
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
...
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
...
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
....
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
....
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
....
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194

    All functionality is part of a class named WhAjaj, and that class
    acts as namespace for this framework.

    Author: Stephan Beal (http://wanderinghorse.net/home/stephan/)

    License: Public Domain

    This framework is directly derived from code originally found in
    http://code.google.com/p/jsonmessage, and later in
    http://whiki.wanderinghorse.net, where it contained quite a bit
    of application-specific logic. It was eventually (the 3rd time i
    needed it) split off into its own library to simplify inclusion
    into my many mini-projects.
*/


/**
    The WhAjaj function is primarily a namespace, and not intended
    to called or instantiated via the 'new' operator.
*/
function WhAjaj()
{
}

/** Returns a millisecond Unix Epoch timestamp. */
................................................................................
WhAjaj.msTimestamp = function()
{
    return (new Date()).getTime();
};

/** Returns a Unix Epoch timestamp (in seconds) in integer format.

    Reminder to self: (1.1 %1.2) evaluates to a floating-point value
    in JS, and thus this implementation is less than optimal.
*/
WhAjaj.unixTimestamp = function()
{
    var ts = (new Date()).getTime();
    return parseInt( ""+((ts / 1000) % ts) );
};
................................................................................
    )
    ;
};

/**
    Parses window.location.search-style string into an object
    containing key/value pairs of URL arguments (already urldecoded).

    If the str argument is not passed (arguments.length==0) then
    window.location.search.substring(1) is used by default. If
    neither str is passed in nor window exists then false is returned.

    On success it returns an Object containing the key/value pairs
    parsed from the string. Keys which have no value are treated
    has having the boolean true value.

    FIXME: for keys in the form "name[]", build an array of results,
    like PHP does.

*/
WhAjaj.processUrlArgs = function(str) {
    if( 0 === arguments.length ) {
        if( ('undefined' === typeof window) ||
            !window.location ||
            !window.location.search )  return false;
        else str = (''+window.location.search).substring(1);
................................................................................
};

/**
    A simple wrapper around JSON.stringify(), using my own personal
    preferred values for the 2nd and 3rd parameters. To globally
    set its indentation level, assign WhAjaj.stringify.indent to
    an integer value (0 for no intendation).

    This function is intended only for human-readable output, not
    generic over-the-wire JSON output (where JSON.stringify(val) will
    produce smaller results).
*/
WhAjaj.stringify = function(val) {
    if( ! arguments.callee.indent ) arguments.callee.indent = 4;
    return JSON.stringify(val,0,arguments.callee.indent);
};

/**
    Each instance of this class holds state information for making
    AJAJ requests to a back-end system. While clients may use one
    "requester" object per connection attempt, for connections to the
    same back-end, using an instance configured for that back-end
    can simplify usage. This class is designed so that the actual
    connection-related details (i.e. _how_ it connects to the
    back-end) may be re-implemented to use a client's preferred
    connection mechanism (e.g. jQuery).

    The optional opt paramater may be an object with any (or all) of
    the properties documented for WhAjaj.Connector.options.ajax.
    Properties set here (or later via modification of the "options"
    property of this object) will be used in calls to
    WhAjaj.Connector.sendRequest(), and these override (normally) any
    options set in WhAjaj.Connector.options.ajax. Note that
    WhAjaj.Connector.sendRequest() _also_ takes an options object,
    and ones passed there will override, for purposes of that one
    request, any options passed in here or defined in
    WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax
    and WhAjaj.Connector.prototype.sendRequest() for more details
    about the precedence of options.

    Sample usage:

    @code
    // Set up common connection-level options:
    var cgi = new WhAjaj.Connector({
        url: '/cgi-bin/my.cgi',
        timeout:10000,
        onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); },
        onError(req,opt) {
................................................................................
        onResponse(resp,req){ alert(WhAjaj.stringify(resp)); }
    });
    @endcode

    For common request types, clients can add functions to this
    object which act as wrappers for backend-specific functionality. As
    a simple example:

    @code
    cgi.login = function(name,pw,ajajOpt) {
        this.sendRequest(
            {command:"json/login",
              name:name,
              password:pw
            }, ajajOpt );
    };
    @endcode

    TODOs:

    - Caching of page-load requests, with a configurable lifetime.

    - Use-cases like the above login() function are a tiny bit
    problematic to implement when each request has a different URL
    path (i know this from the whiki and fossil implementations).
    This is partly a side-effect of design descisions made back in
    the very first days of this code's life. i need to go through
    and see where i can bend those conventions a bit (where it won't
    break my other apps unduly).
................................................................................
WhAjaj.Connector.options = {
    /**
        A (meaningless) prefix to apply to WhAjaj.Connector-generated
        request IDs.
    */
    requestIdPrefix:'WhAjaj.Connector-',
    /**
        Default options for WhAjaj.Connector.sendRequest() connection
        parameters. This object holds only connection-related
        options and callbacks (all optional), and not options
        related to the required JSON structure of any given request.
        i.e. the page name used in a get-page request are not set
        here but are specified as part of the request object.

        These connection options are a "normalized form" of options
        often found in various AJAX libraries like jQuery,
        Prototype, dojo, etc. This approach allows us to swap out
        the real connection-related parts by writing a simple proxy
        which transforms our "normalized" form to the
        backend-specific form. For examples, see the various
        implementations stored in WhAjaj.Connector.sendImpls.

        The following callback options are, in practice, almost
        always set globally to some app-wide defaults:

        - onError() to report errors using a common mechanism.
        - beforeSend() to start a visual activity notification
        - afterSend() to disable the visual activity notification

        However, be aware that if any given WhAjaj.Connector instance is
        given its own before/afterSend callback then those will
        override these. Mixing shared/global and per-instance
        callbacks can potentially lead to confusing results if, e.g.,
        the beforeSend() and afterSend() functions have side-effects
        but are not used with their proper before/after partner.

        TODO: rename this to 'ajaj' (the name is historical). The
        problem with renaming it is is that the word 'ajax' is
        pretty prevelant in the source tree, so i can't globally
        swap it out.
    */
    ajax: {
        /**
            URL of the back-end server/CGI.
        */
        url: '/some/path',

        /**
            Connection method. Some connection-related functions might
            override any client-defined setting.

            Must be one of 'GET' or 'POST'. For custom connection
            implementation, it may optionally be some
            implementation-specified value.

            Normally the API can derive this value automatically - if the
            request uses JSON data it is POSTed, else it is GETted.
        */
        method:'GET',

        /**
            A hint whether to run the operation asynchronously or
            not. Not all concrete WhAjaj.Connector.sendImpl()
            implementations can support this. Interestingly, at
            least one popular AJAX toolkit does not document
            supporting _synchronous_ AJAX operations. All common
            browser-side implementations support async operation, but
            non-browser implementations might not.
        */
        asynchronous:true,

        /**
            A HTTP authentication login name for the AJAX
            connection. Not all concrete WhAjaj.Connector.sendImpl()
            implementations can support this.
        */
        loginName:undefined,

        /**
            An HTTP authentication login password for the AJAJ
            connection. Not all concrete WhAjaj.Connector.sendImpl()
            implementations can support this.
        */
        loginPassword:undefined,

        /**
            A connection timeout, in milliseconds, for establishing
            an AJAJ connection. Not all concrete
            WhAjaj.Connector.sendImpl() implementations can support this.
        */
        timeout:10000,

        /**
            If an AJAJ request receives JSON data from the back-end,
            that data is passed as a plain Object as the response
................................................................................
            ignore it (only those which need a way to map specific
            requests to responses will need it). The 3rd parameter
            is the same as the 'this' object for the context of the
            callback, but is provided because the instance-level
            callbacks (set in (WhAjaj.Connector instance).callbacks,
            require it in some cases (because their 'this' is
            different!).

            Note that the response might contain error information
            which comes from the back-end. The difference between
            this error info and the info passed to the onError()
            callback is that this data indicates an
            application-level error, whereas onError() is used to
            report connection-level problems or when the backend
            produces non-JSON data (which, when not in jsonp mode,
            is unexpected and is as fatal to the request as a
            connection error).
        */
        onResponse: function(response, request, opt){},

        /**
            If an AJAX request fails to establish a connection or it
            receives non-JSON data from the back-end, this function
            is called (e.g. timeout error or host name not
            resolvable). It is passed the originating request and the
            "normalized" connection parameters used for that
            request. The connectOpt object "should" (or "might")
            have an "errorMessage" property which describes the
            nature of the problem.

            Clients will almost always want to replace the default
            implementation with something which integrates into
            their application.
        */
        onError: function(request, connectOpt)
        {
            alert('AJAJ request failed:\n'
                +'Connection information:\n'
                +JSON.stringify(connectOpt,0,4)
            );
        },

        /**
            Called before each connection attempt is made. Clients
            can use this to, e.g.,  enable a visual "network activity
            notification" for the user. It is passed the original
            request object and the normalized connection parameters
            for the request. If this function changes opt, those
            changes _are_ applied to the subsequent request. If this
            function throws, neither the onError() nor afterSend()
            callbacks are triggered and WhAjaj.Connector.sendImpl()
            propagates the exception back to the caller.
        */
        beforeSend: function(request,opt){},

        /**
            Called after an AJAJ connection attempt completes,
            regardless of success or failure. Passed the same
            parameters as beforeSend() (see that function for
            details).

            Here's an example of setting up a visual notification on
            ajax operations using jQuery (but it's also easy to do
            without jQuery as well):

            @code
            function startAjaxNotif(req,opt) {
                var me = arguments.callee;
                var c = ++me.ajaxCount;
                me.element.text( c + " pending AJAX operation(s)..." );
                if( 1 == c ) me.element.stop().fadeIn();
            }
            startAjaxNotif.ajaxCount = 0.
            startAjaxNotif.element = jQuery('#whikiAjaxNotification');

            function endAjaxNotif() {
                var c = --startAjaxNotif.ajaxCount;
                startAjaxNotif.element.text( c+" pending AJAX operation(s)..." );
                if( 0 == c ) startAjaxNotif.element.stop().fadeOut();
            }
            @endcode

            Set the beforeSend/afterSend properties to those
            functions to enable the notifications by default.
        */
        afterSend: function(request,opt){},

        /**
            If jsonp is a string then the WhAjaj-internal response
            handling code ASSUMES that the response contains a JSONP-style
            construct and eval()s it after afterSend() but before onResponse().
            In this case, onResponse() will get a string value for the response
            instead of a response object parsed from JSON.
        */
        jsonp:undefined,
        /**
            Don't use yet. Planned future option.
        */
        propagateExceptions:false
    }
................................................................................
    else v = this.options[key];
    if( undefined !== v ) return v;
    else v = WhAjaj.Connector.options.ajax[key];
    return v;
};

/**
    Returns a unique string on each call containing a generic
    reandom request identifier string. This is not used by the core
    API but can be used by client code to generate unique IDs for
    each request (if needed).

    The exact format is unspecified and may change in the future.

    Request IDs can be used by clients to "match up" responses to
    specific requests if needed. In practice, however, they are
    seldom, if ever, needed. When passing several concurrent
    requests through the same response callback, it might be useful
    for some clients to be able to distinguish, possibly re-routing
    them through other handlers based on the originating request type.

    If this.options.requestIdPrefix or
    WhAjaj.Connector.options.requestIdPrefix is set then that text
    is prefixed to the returned string.
*/
WhAjaj.Connector.prototype.generateRequestId = function()
{
    if( undefined === arguments.callee.sequence )
    {
................................................................................
        if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */;
        this.options[k] = opt[k];
    }
    return this.options;
};

/**
    An internal helper object which holds several functions intended
    to simplify the creation of concrete communication channel
    implementations for WhAjaj.Connector.sendImpl(). These operations
    take care of some of the more error-prone parts of ensuring that
    onResponse(), onError(), etc. callbacks are called consistently
    using the same rules.
*/
WhAjaj.Connector.sendHelper = {
    /**
        opt is assumed to be a normalized set of
        WhAjaj.Connector.sendRequest() options. This function
        creates a url by concatenating opt.url and some form of
        opt.urlParam.

        If opt.urlParam is an object or string then it is appended
        to the url. An object is assumed to be a one-dimensional set
        of simple (urlencodable) key/value pairs, and not larger
        data structures. A string value is assumed to be a
        well-formed, urlencoded set of key/value pairs separated by
        '&' characters.

        The new/normalized URL is returned (opt is not modified). If
        opt.urlParam is not set then opt.url is returned (or an
        empty string if opt.url is itself a false value).

        TODO: if opt is-a Object and any key points to an array,
        build up a list of keys in the form "keyname[]". We could
        arguably encode sub-objects like "keyname[subkey]=...", but
        i don't know if that's conventions-compatible with other
        frameworks.
    */
    normalizeURL: function(opt) {
        var u = opt.url || '';
        if( opt.urlParam ) {
            var addQ = (u.indexOf('?') >= 0) ? false : true;
            var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false);
................................................................................
                tail = opt.urlParam;
            }
            u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail;
        }
        return u;
    },
    /**
        Should be called by WhAjaj.Connector.sendImpl()
        implementations after a response has come back. This
        function takes care of most of ensuring that framework-level
        conventions involving WhAjaj.Connector.options.ajax
        properties are followed.

        The request argument must be the original request passed to
        the sendImpl() function. It may legally be null for GET requests.

        The opt object should be the normalized AJAX options used
        for the connection.

        The resp argument may be either a plain Object or a string
        (in which case it is assumed to be JSON).

        The 'this' object for this call MUST be a WhAjaj.Connector
        instance in order for callback processing to work properly.

        This function takes care of the following:

        - Calling opt.afterSend()

        - If resp is a string, de-JSON-izing it to an object.

        - Calling opt.onResponse()

        - Calling opt.onError() in several common (potential) error
        cases.

        - If resp is-a String and opt.jsonp then resp is assumed to be
        a JSONP-form construct and is eval()d BEFORE opt.onResponse()
        is called. It is arguable to eval() it first, but the logic
        integrates better with the non-jsonp handler.

        The sendImpl() should return immediately after calling this.

        The sendImpl() must call only one of onSendSuccess() or
        onSendError(). It must call one of them or it must implement
        its own response/error handling, which is not recommended
        because getting the documented semantics of the
        onError/onResponse/afterSend handling correct can be tedious.
    */
    onSendSuccess:function(request,resp,opt) {
        var cb = this.callbacks || {};
        if( WhAjaj.isFunction(cb.afterSend) ) {
            try {cb.afterSend( request, opt );}
            catch(e){}
................................................................................
    },
   /**
        Should be called by sendImpl() implementations after a response
        has failed to connect (e.g. could not resolve host or timeout
        reached). This function takes care of most of ensuring that
        framework-level conventions involving WhAjaj.Connector.options.ajax
        properties are followed.

        The request argument must be the original request passed to
        the sendImpl() function. It may legally be null for GET
        requests.

        The 'this' object for this call MUST be a WhAjaj.Connector
        instance in order for callback processing to work properly.

        The opt object should be the normalized AJAX options used
        for the connection. By convention, the caller of this
        function "should" set opt.errorMessage to contain a
        human-readable description of the error.

        The sendImpl() should return immediately after calling this. The
        return value from this function is unspecified.
    */
    onSendError: function(request,opt) {
        var cb = this.callbacks || {};
        if( WhAjaj.isFunction(cb.afterSend) ) {
            try {cb.afterSend( request, opt );}
................................................................................
            try {opt.onError( request, opt );}
            catch(e) {/*ignore*/}
        }
    }
};

/**
    WhAjaj.Connector.sendImpls holds several concrete
    implementations of WhAjaj.Connector.prototype.sendImpl(). To use
    a specific implementation by default assign
    WhAjaj.Connector.prototype.sendImpl to one of these functions.

    The functions defined here require that the 'this' object be-a
    WhAjaj.Connector instance.

    Historical notes:

    a) We once had an implementation based on Prototype, but that
    library just pisses me off (they change base-most types'
    prototypes, introducing side-effects in client code which
    doesn't even use Prototype). The Prototype version at the time
    had a serious toJSON() bug which caused empty arrays to
    serialize as the string "[]", which broke a bunch of my code.
    (That has been fixed in the mean time, but i don't use
    Prototype.)

    b) We once had an implementation for the dojo library,

    If/when the time comes to add Prototype/dojo support, we simply
    need to port:

    http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js

    (search that file for "dojo" and "Prototype") to this tree. That
    code is this code's generic grandfather and they are still very
    similar, so a port is trivial.

*/
WhAjaj.Connector.sendImpls = {
    /**
        This is a concrete implementation of
        WhAjaj.Connector.prototype.sendImpl() which uses the
        environment's native XMLHttpRequest class to send whiki
        requests and fetch the responses.

        The only argument must be a connection properties object, as
        constructed by WhAjaj.Connector.normalizeAjaxParameters().

        If window.firebug is set then window.firebug.watchXHR() is
        called to enable monitoring of the XMLHttpRequest object.

        This implementation honors the loginName and loginPassword
        connection parameters.

        Returns the XMLHttpRequest object.

        This implementation requires that the 'this' object be-a
        WhAjaj.Connector.

        This implementation uses setTimeout() to implement the
        timeout support, and thus the JS engine must provide that
        functionality.
    */
    XMLHttpRequest: function(request, args)
    {
        var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request;
        var xhr = new XMLHttpRequest();
        var startTime = (new Date()).getTime();
................................................................................
        {
            args.errorMessage = e.toString();
            WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
            return undefined;
        }
    }/*XMLHttpRequest()*/,
    /**
        This is a concrete implementation of
        WhAjaj.Connector.prototype.sendImpl() which uses the jQuery
        AJAX API to send requests and fetch the responses.

        The first argument may be either null/false, an Object
        containing toJSON-able data to post to the back-end, or such an
        object in JSON string form.

        The second argument must be a connection properties object, as
        constructed by WhAjaj.Connector.normalizeAjaxParameters().

        If window.firebug is set then window.firebug.watchXHR() is
        called to enable monitoring of the XMLHttpRequest object.

        This implementation honors the loginName and loginPassword
        connection parameters.

        Returns the XMLHttpRequest object.

        This implementation requires that the 'this' object be-a
        WhAjaj.Connector.
    */
    jQuery:function(request,args)
    {
        var data = request || undefined;
        var whself = this;
        if( data ) {
................................................................................
        {
            args.errorMessage = e.toString();
            WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] );
            return undefined;
        }
    }/*jQuery()*/,
    /**
        This is a concrete implementation of
        WhAjaj.Connector.prototype.sendImpl() which uses the rhino
        Java API to send requests and fetch the responses.

        Limitations vis-a-vis the interface:

        - timeouts are not supported.

        - asynchronous mode is not supported because implementing it
................................................................................
        json = json.join('');
        //print("READ IN JSON: "+json);
        WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] );
    }/*rhino*/
};

/**
    An internal function which takes an object containing properties
    for a WhAjaj.Connector network request. This function creates a new
    object containing a superset of the properties from:

    a) opt
    b) this.options
    c) WhAjaj.Connector.options.ajax

    in that order, using the first one it finds.

    All non-function properties are _deeply_ copied via JSON cloning
    in order to prevent accidental "cross-request pollenation" (been
    there, done that). Functions cannot be cloned and are simply
    copied by reference.

    This function throws if JSON-copying one of the options fails
    (e.g. due to cyclic data structures).

    Reminder to self: this function does not "normalize" opt.urlParam
    by encoding it into opt.url, mainly for historical reasons, but
    also because that behaviour was specifically undesirable in this
    code's genetic father.
*/
WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt)
{
................................................................................
    cp( this.options );
    cp( WhAjaj.Connector.options.ajax );
    // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc);
    return rc;
};

/**
    This is the generic interface for making calls to a back-end
    JSON-producing request handler. It is a simple wrapper around
    WhAjaj.Connector.prototype.sendImpl(), which just normalizes the
    connection options for sendImpl() and makes sure that
    opt.beforeSend() is (possibly) called.

    The request parameter must either be false/null/empty or a
    fully-populated JSON-able request object (which will be sent as
    unencoded application/json text), depending on the type of
    request being made. It is never semantically legal (in this API)
    for request to be a string/number/true/array value. As a rule,
    only POST requests use the request data. GET requests should
    encode their data in opt.url or opt.urlParam (see below).

    opt must contain the network-related parameters for the request.
    Paramters _not_ set in opt are pulled from this.options or
    WhAjaj.Connector.options.ajax (in that order, using the first
    value it finds). Thus the set of connection-level options used
    for the request are a superset of those various sources.

    The "normalized" (or "superimposed") opt object's URL may be
    modified before the request is sent, as follows:

    if opt.urlParam is a string then it is assumed to be properly
    URL-encoded parameters and is appended to the opt.url. If it is
    an Object then it is assumed to be a one-dimensional set of
    key/value pairs with simple values (numbers, strings, booleans,
    null, and NOT objects/arrays). The keys/values are URL-encoded
    and appended to the URL.

    The beforeSend() callback (see below) can modify the options
    object before the request attempt is made.

    The callbacks in the normalized opt object will be triggered as
    follows (if they are set to Function values):

    - beforeSend(request,opt) will be called before any network
    processing starts. If beforeSend() throws then no other
    callbacks are triggered and this function propagates the
    exception. This function is passed normalized connection options
    as its second parameter, and changes this function makes to that
    object _will_ be used for the pending connection attempt.

    - onError(request,opt) will be called if a connection to the
    back-end cannot be established. It will be passed the original
    request object (which might be null, depending on the request
    type) and the normalized options object. In the error case, the
    opt object passed to onError() "should" have a property called
    "errorMessage" which contains a description of the problem.

    - onError(request,opt) will also be called if connection
    succeeds but the response is not JSON data.

    - onResponse(response,request) will be called if the response
    returns JSON data. That data might hold an error response code -
    clients need to check for that. It is passed the response object
    (a plain object) and the original request object.

    - afterSend(request,opt) will be called directly after the
    AJAX request is finished, before onError() or onResonse() are
    called. Possible TODO: we explicitly do NOT pass the response to
    this function in order to keep the line between the responsibilities
    of the various callback clear (otherwise this could be used the same
    as onResponse()). In practice it would sometimes be useful have the
    response passed to this function, mainly for logging/debugging
................................................................................
{
    if( !WhAjaj.isFunction(this.sendImpl) )
    {
        throw new Error("This object has no sendImpl() member function! I don't know how to send the request!");
    }
    var ex = false;
    var av = Array.prototype.slice.apply( arguments, [0] );

    /**
        FIXME: how to handle the error, vis-a-vis- the callbacks, if
        normalizeAjaxParameters() throws? It can throw if
        (de)JSON-izing fails.
    */
    var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} );
    norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm);
    if( ! request ) norm.method = 'GET';
    var cb = this.callbacks || {};
    if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) {

Changes to ajax/wiki-editor.html.

275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
            onResponse:function(resp,req){
                TheApp.onResponse(resp,req);
                if(resp.resultCode) return;
                delete p.isNew;
                p.timestamp = resp.payload.timestamp;
            }
        });
        
    };

    TheApp.createNewPage = function(){
        var name = prompt("New page name?");
        if(!name) return;
        var p = {
            name:name,







|







275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
            onResponse:function(resp,req){
                TheApp.onResponse(resp,req);
                if(resp.resultCode) return;
                delete p.isNew;
                p.timestamp = resp.payload.timestamp;
            }
        });

    };

    TheApp.createNewPage = function(){
        var name = prompt("New page name?");
        if(!name) return;
        var p = {
            name:name,

Changes to src/fshell.c.

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
110
111
112
113
114
115
116
117
118
    /* If the --debug flag was used, display the parsed arguments */
    if( fDebug ){
      for(i=1; i<nArg; i++){
        fossil_print("argv[%d] = [%s]\n", i, azArg[i]);
      }
    }

    /* Special cases */    
    if( nArg<2 ) continue;
    if( fossil_strcmp(azArg[1],"exit")==0 ) break;

    /* Fork a process to handle the command */
    childPid = fork();
    if( childPid<0 ){
      printf("could not fork a child process to handle the command\n");
................................................................................
      exit(0);
    }else{
      /* The parent process */
      int status;
      waitpid(childPid, &status, 0);
    }
  }
#endif 
}







|







 







|

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
110
111
112
113
114
115
116
117
118
    /* If the --debug flag was used, display the parsed arguments */
    if( fDebug ){
      for(i=1; i<nArg; i++){
        fossil_print("argv[%d] = [%s]\n", i, azArg[i]);
      }
    }

    /* Special cases */
    if( nArg<2 ) continue;
    if( fossil_strcmp(azArg[1],"exit")==0 ) break;

    /* Fork a process to handle the command */
    childPid = fork();
    if( childPid<0 ){
      printf("could not fork a child process to handle the command\n");
................................................................................
      exit(0);
    }else{
      /* The parent process */
      int status;
      waitpid(childPid, &status, 0);
    }
  }
#endif
}

Changes to src/shun.c.

491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
    cnt = 0;
    if( PB("uvdelete") && PB("confirmdelete") ){
      db_multi_exec(
        "DELETE FROM unversioned WHERE rcvid=%d", rcvid
      );
    }
    db_finalize(&q);
    db_prepare(&q, 
      "SELECT name, hash, sz\n"
      "  FROM unversioned "
      " WHERE rcvid=%d", rcvid
     );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q,0);
      const char *zHash = db_column_text(&q,1);
      int size = db_column_int(&q,2);
      int isDeleted = zHash==0;
      if( cnt==0 ){
        @ <tr><th valign="top" align="right">Unversioned&nbsp;Files:</th>







|



|







491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
    cnt = 0;
    if( PB("uvdelete") && PB("confirmdelete") ){
      db_multi_exec(
        "DELETE FROM unversioned WHERE rcvid=%d", rcvid
      );
    }
    db_finalize(&q);
    db_prepare(&q,
      "SELECT name, hash, sz\n"
      "  FROM unversioned "
      " WHERE rcvid=%d", rcvid
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q,0);
      const char *zHash = db_column_text(&q,1);
      int size = db_column_int(&q,2);
      int isDeleted = zHash==0;
      if( cnt==0 ){
        @ <tr><th valign="top" align="right">Unversioned&nbsp;Files:</th>

Changes to src/timeline.c.

2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
**
**     before
**     after
**     descendants | children
**     ancestors | parents
**
** The CHECKIN can be any unique prefix of 4 characters or more. You
** can also say "current" for the current version. 
**
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, the "T" may be replaced by
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z"
** means UTC.
**







|







2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
**
**     before
**     after
**     descendants | children
**     ancestors | parents
**
** The CHECKIN can be any unique prefix of 4 characters or more. You
** can also say "current" for the current version.
**
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, the "T" may be replaced by
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z"
** means UTC.
**

Changes to src/unversioned.c.

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
** SQL code to implement the tables needed by the unversioned.
*/
static const char zUnversionedInit[] =
@ CREATE TABLE IF NOT EXISTS repository.unversioned(
@   name TEXT PRIMARY KEY,       -- Name of the uv file
@   rcvid INTEGER,               -- Where received from
@   mtime DATETIME,              -- timestamp.  Seconds since 1970.
@   hash TEXT,                   -- Content hash.  NULL if a delete marker 
@   sz INTEGER,                  -- size of content after decompression
@   encoding INT,                -- 0: plaintext.  1: zlib compressed
@   content BLOB                 -- content of the file.  NULL if oversized
@ ) WITHOUT ROWID;
;

/*







|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
** SQL code to implement the tables needed by the unversioned.
*/
static const char zUnversionedInit[] =
@ CREATE TABLE IF NOT EXISTS repository.unversioned(
@   name TEXT PRIMARY KEY,       -- Name of the uv file
@   rcvid INTEGER,               -- Where received from
@   mtime DATETIME,              -- timestamp.  Seconds since 1970.
@   hash TEXT,                   -- Content hash.  NULL if a delete marker
@   sz INTEGER,                  -- size of content after decompression
@   encoding INT,                -- 0: plaintext.  1: zlib compressed
@   content BLOB                 -- content of the file.  NULL if oversized
@ ) WITHOUT ROWID;
;

/*

Changes to src/xfer.c.

291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
...
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
....
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
** unversioned file to the "unversioned" table.
**
** The file line is in one of the following two forms:
**
**      uvfile NAME MTIME HASH SIZE FLAGS
**      uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
**
** If the 0x0001 bit of FLAGS is set, that means the file has been 
** deleted, SIZE is zero, the HASH is "-", and the "\n CONTENT" is omitted.
**
** SIZE is the number of bytes of CONTENT.  The CONTENT is uncompressed.
** HASH is the SHA1 hash of CONTENT.
**
** If the 0x0004 bit of FLAGS is set, that means the CONTENT is omitted.
** The sender might have omitted the content because it is too big to
................................................................................
  if( !isWriter ) goto end_accept_unversioned_file;

  /* Make sure we have a valid g.rcvid marker */
  content_rcvid_init();

  /* Check to see if current content really should be overwritten.  Ideally,
  ** a uvfile card should never have been sent unless the overwrite should
  ** occur.  But do not trust the sender.  Double-check. 
  */
  iStatus = unversioned_status(blob_str(&pXfer->aToken[1]), mtime,
                               blob_str(pHash));
  if( iStatus>=3 ) goto end_accept_unversioned_file;
  
  /* Store the content */
  isDelete = blob_eq(pHash, "-");
  if( isDelete ){
    db_prepare(&q,
      "UPDATE unversioned"
      "   SET rcvid=:rcvid, mtime=:mtime, hash=NULL,"
      "       sz=0, encoding=0, content=NULL"
................................................................................
    while( db_step(&uvq)==SQLITE_ROW ){
      const char *zName = db_column_text(&uvq,0);
      sqlite3_int64 mtime = db_column_int64(&uvq,1);
      const char *zHash = db_column_text(&uvq,2);
      int sz = db_column_int(&uvq,3);
      nUvIgot++;
      if( zHash==0 ){ sz = 0; zHash = "-"; }
      blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", 
                   zName, mtime, zHash, sz);
    }
    db_finalize(&uvq);
  }
}

/*







|







 







|




|







 







|







291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
...
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
....
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
** unversioned file to the "unversioned" table.
**
** The file line is in one of the following two forms:
**
**      uvfile NAME MTIME HASH SIZE FLAGS
**      uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
**
** If the 0x0001 bit of FLAGS is set, that means the file has been
** deleted, SIZE is zero, the HASH is "-", and the "\n CONTENT" is omitted.
**
** SIZE is the number of bytes of CONTENT.  The CONTENT is uncompressed.
** HASH is the SHA1 hash of CONTENT.
**
** If the 0x0004 bit of FLAGS is set, that means the CONTENT is omitted.
** The sender might have omitted the content because it is too big to
................................................................................
  if( !isWriter ) goto end_accept_unversioned_file;

  /* Make sure we have a valid g.rcvid marker */
  content_rcvid_init();

  /* Check to see if current content really should be overwritten.  Ideally,
  ** a uvfile card should never have been sent unless the overwrite should
  ** occur.  But do not trust the sender.  Double-check.
  */
  iStatus = unversioned_status(blob_str(&pXfer->aToken[1]), mtime,
                               blob_str(pHash));
  if( iStatus>=3 ) goto end_accept_unversioned_file;

  /* Store the content */
  isDelete = blob_eq(pHash, "-");
  if( isDelete ){
    db_prepare(&q,
      "UPDATE unversioned"
      "   SET rcvid=:rcvid, mtime=:mtime, hash=NULL,"
      "       sz=0, encoding=0, content=NULL"
................................................................................
    while( db_step(&uvq)==SQLITE_ROW ){
      const char *zName = db_column_text(&uvq,0);
      sqlite3_int64 mtime = db_column_int64(&uvq,1);
      const char *zHash = db_column_text(&uvq,2);
      int sz = db_column_int(&uvq,3);
      nUvIgot++;
      if( zHash==0 ){ sz = 0; zHash = "-"; }
      blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
                   zName, mtime, zHash, sz);
    }
    db_finalize(&uvq);
  }
}

/*

Changes to test/amend.test.

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
  incr tc
  set tags {}
  set cancels {}
  set t1exp ""
  set t2exp "*"
  set t3exp "*"
  set t5exp "*"
  foreach tag $tagt { 
    lappend tags -tag $tag
    lappend cancels -cancel $tag
  }
  foreach res $result {
    append t1exp ", $res"
    append t2exp "sym-$res*"
    append t3exp "Add*tag*\"$res\".*"







|







302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
  incr tc
  set tags {}
  set cancels {}
  set t1exp ""
  set t2exp "*"
  set t3exp "*"
  set t5exp "*"
  foreach tag $tagt {
    lappend tags -tag $tag
    lappend cancels -cancel $tag
  }
  foreach res $result {
    append t1exp ", $res"
    append t2exp "sym-$res*"
    append t3exp "Add*tag*\"$res\".*"

Changes to test/revert.test.

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Test 'fossil revert' against expected results from 'fossil changes' and
# 'fossil addremove -n', as well as by verifying the existence of files
# on the file system. 'fossil undo' is called after each test
#
proc revert-test {testid revertArgs expectedRevertOutput args} {
  global RESULT
  set passed 1
  
  set args [dict merge {
    -changes {} -addremove {} -exists {} -notexists {}
  } $args]
  
  set result [fossil revert {*}$revertArgs]
  test_status_list revert-$testid $result $expectedRevertOutput
  
  set statusListTests [list -changes changes -addremove {addremove -n}]
  foreach {key fossilArgs} $statusListTests {
    set expected [dict get $args $key]
    set result [fossil {*}$fossilArgs] 
    test_status_list revert-$testid$key $result $expected
  }
  
  set fileExistsTests [list -exists 1 does -notexists 0 should]
  foreach {key expected verb} $fileExistsTests {
    foreach path [dict get $args $key] {
      if {[file exists $path] != $expected} {
        set passed 0
        protOut "  Failure: File $verb not exist: $path"
      }
    }
    test revert-$testid$key $passed
  }
  
  fossil undo
}

require_no_open_checkout
test_setup

# Prepare first commit







|



|


|



|


|










|







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Test 'fossil revert' against expected results from 'fossil changes' and
# 'fossil addremove -n', as well as by verifying the existence of files
# on the file system. 'fossil undo' is called after each test
#
proc revert-test {testid revertArgs expectedRevertOutput args} {
  global RESULT
  set passed 1

  set args [dict merge {
    -changes {} -addremove {} -exists {} -notexists {}
  } $args]

  set result [fossil revert {*}$revertArgs]
  test_status_list revert-$testid $result $expectedRevertOutput

  set statusListTests [list -changes changes -addremove {addremove -n}]
  foreach {key fossilArgs} $statusListTests {
    set expected [dict get $args $key]
    set result [fossil {*}$fossilArgs]
    test_status_list revert-$testid$key $result $expected
  }

  set fileExistsTests [list -exists 1 does -notexists 0 should]
  foreach {key expected verb} $fileExistsTests {
    foreach path [dict get $args $key] {
      if {[file exists $path] != $expected} {
        set passed 0
        protOut "  Failure: File $verb not exist: $path"
      }
    }
    test revert-$testid$key $passed
  }

  fossil undo
}

require_no_open_checkout
test_setup

# Prepare first commit

Changes to test/stash.test.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
...
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#   drh@hwaci.com
#   http://www.hwaci.com/drh/
#
############################################################################
#
#
# Tests for 'fossil stash'
# 
#

proc knownBug {t tests} {
  return [expr {$t in $tests ? "knownBug" : ""}]
}

# Test 'fossil stash' against expected results from 'fossil changes' and
................................................................................
#  -notexists  One or more listed files do exist
#
# Also, if the exit status of fossil stash does not match
# expectations, the rest of the areas are not tested.
proc test_result_state {testid cmdArgs expectedOutput args} {
  global RESULT
  set passed 1
  
  set args [dict merge {
    -changes {} -addremove {} -exists {} -notexists {} -knownbugs {}
  } $args]

  set knownbugs [dict get $args "-knownbugs"]
  set result $::RESULT
  set code $::CODE
................................................................................
  } else {
    test $testid-CODE {!$code}  [knownBug "-code" $knownbugs]
    if {$code} {
      return
    }
  }
  test_status_list $testid $result $expectedOutput [knownBug "-result" $knownbugs]
  
  set statusListTests [list -changes changes -addremove {addremove -n}]
  foreach {key fossilArgs} $statusListTests {
    set expected [dict get $args $key]
    set result [fossil {*}$fossilArgs] 
    test_status_list $testid$key $result $expected [knownBug $key $knownbugs]
  }
  
  set fileExistsTests [list -exists 1 does -notexists 0 should]
  foreach {key expected verb} $fileExistsTests {
    foreach path [dict get $args $key] {
      if {[file exists $path] != $expected} {
        set passed 0
        protOut "  Failure: File $verb not exist: $path"
      }
    }
    test $testid$key $passed [knownBug $key $knownbugs]
  }
  
  #fossil undo
}

proc stash-test {testid stashArgs expectedStashOutput args} {
  fossil stash {*}$stashArgs
  return [test_result_state stash-$testid "stash $stashArgs" $expectedStashOutput {*}$args] 
}

require_no_open_checkout
test_setup

# Prepare first commit
#
................................................................................
stash-test 2-1 {save -m "f1b"} {
  REVERT   f1
  DELETE   f1n
} -exists {f1} -notexists {f1n} -knownbugs {-code -result}
# TODO: add tests that verify the saved stash is sensible. Possibly
# by applying it and checking results. But until the MISSING file
# error is fixed, there is nothing stashed to test.
 

# Test stashing a newly added (but never committed) file. As with
# fossil revert, fossil stash save unmanages the new file, but 
# leaves the copy present on disk. This is undocumented, but 
# probably sensible.
test_setup
write_file f1 "f1"
write_file f2 "f2"
fossil add f1 f2
fossil commit -m "baseline"

................................................................................
} -addremove {
} -exists {f1 f2 f3} -notexists {}
fossil status


# Test stashing a rename of one file with at least one file
# unchanged. This should stash (and revert) just the rename
# operation. Instead it also stores and touches the unchanged file. 
test_setup
write_file f1 "f1"
write_file f2 "f2"
fossil add f1 f2
fossil commit -m "baseline"

fossil mv --hard f2 f2n







|







 







|







 







|



|


|










|





|







 







|


|
|







 







|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
...
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#   drh@hwaci.com
#   http://www.hwaci.com/drh/
#
############################################################################
#
#
# Tests for 'fossil stash'
#
#

proc knownBug {t tests} {
  return [expr {$t in $tests ? "knownBug" : ""}]
}

# Test 'fossil stash' against expected results from 'fossil changes' and
................................................................................
#  -notexists  One or more listed files do exist
#
# Also, if the exit status of fossil stash does not match
# expectations, the rest of the areas are not tested.
proc test_result_state {testid cmdArgs expectedOutput args} {
  global RESULT
  set passed 1

  set args [dict merge {
    -changes {} -addremove {} -exists {} -notexists {} -knownbugs {}
  } $args]

  set knownbugs [dict get $args "-knownbugs"]
  set result $::RESULT
  set code $::CODE
................................................................................
  } else {
    test $testid-CODE {!$code}  [knownBug "-code" $knownbugs]
    if {$code} {
      return
    }
  }
  test_status_list $testid $result $expectedOutput [knownBug "-result" $knownbugs]

  set statusListTests [list -changes changes -addremove {addremove -n}]
  foreach {key fossilArgs} $statusListTests {
    set expected [dict get $args $key]
    set result [fossil {*}$fossilArgs]
    test_status_list $testid$key $result $expected [knownBug $key $knownbugs]
  }

  set fileExistsTests [list -exists 1 does -notexists 0 should]
  foreach {key expected verb} $fileExistsTests {
    foreach path [dict get $args $key] {
      if {[file exists $path] != $expected} {
        set passed 0
        protOut "  Failure: File $verb not exist: $path"
      }
    }
    test $testid$key $passed [knownBug $key $knownbugs]
  }

  #fossil undo
}

proc stash-test {testid stashArgs expectedStashOutput args} {
  fossil stash {*}$stashArgs
  return [test_result_state stash-$testid "stash $stashArgs" $expectedStashOutput {*}$args]
}

require_no_open_checkout
test_setup

# Prepare first commit
#
................................................................................
stash-test 2-1 {save -m "f1b"} {
  REVERT   f1
  DELETE   f1n
} -exists {f1} -notexists {f1n} -knownbugs {-code -result}
# TODO: add tests that verify the saved stash is sensible. Possibly
# by applying it and checking results. But until the MISSING file
# error is fixed, there is nothing stashed to test.


# Test stashing a newly added (but never committed) file. As with
# fossil revert, fossil stash save unmanages the new file, but
# leaves the copy present on disk. This is undocumented, but
# probably sensible.
test_setup
write_file f1 "f1"
write_file f2 "f2"
fossil add f1 f2
fossil commit -m "baseline"

................................................................................
} -addremove {
} -exists {f1 f2 f3} -notexists {}
fossil status


# Test stashing a rename of one file with at least one file
# unchanged. This should stash (and revert) just the rename
# operation. Instead it also stores and touches the unchanged file.
test_setup
write_file f1 "f1"
write_file f2 "f2"
fossil add f1 f2
fossil commit -m "baseline"

fossil mv --hard f2 f2n

Changes to test/wiki.test.

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
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
266
267
268
269
270
271
272
...
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

###############################################################################
# Trying to add a technote with the same timestamp should succeed and create a
# second tech note
fossil wiki create 2ndnote f3 -technote {2016-01-01 12:34}
test wiki-13 {$CODE == 0}
fossil wiki list --technote
set technotelist [split $RESULT "\n"] 
test wiki-13.1 {[llength $technotelist] == 2}

###############################################################################
# commiting a change to an existing technote should replace the page on export
# (this should update the tech note from wiki-13 as that the most recently
# updated one, that should also be the one exported by the export command)
write_file f4 "technote 2nd variant"
................................................................................

###############################################################################
# But we shouldn't be able to update non-existant pages
fossil wiki commit doesntexist f1 -expectError
test wiki-16 {$CODE != 0}

###############################################################################
# Check specifying tags for a technote is OK 
write_file f5 "technote with tags"
fossil wiki create {tagged technote} f5 --technote {2016-01-02 12:34} --technote-tags {A B}
test wiki-17 {$CODE == 0}
write_file f5.1 "editted and tagged technote"
fossil wiki commit {tagged technote} f5 --technote {2016-01-02 12:34} --technote-tags {C D}
test wiki-18 {$CODE == 0}

................................................................................
write_file f8 "Contents of a 'unique' tech note"
fossil wiki create {Unique technote} f8 --technote {2016-01-05 01:02:03}
fossil timeline
test wiki-30 {[string match *Unique*technote* $RESULT]}

###############################################################################
# Check for a collision between an attachment and a note, this was a
# bug that resulted from some code treating the attachment entry as if it 
# were a technote when it isn't really. 
#
# First, wait for the top of the next second so the attachment
# happens at a known time, then add an attachment to an existing note
# and a new note immediately after. 

set t0 [clock seconds]
while {$t0 == [clock seconds]} {
  after 100
}
set t1 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
write_file f9 "Timestamp: $t1"
................................................................................
# "now" is a valid stamp.
set t2 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
write_file f10 "Even unstampted notes are delivered.\nStamped $t2"
fossil wiki create "Unstamped Note" f10 --technote -expectError
test wiki-33 {$CODE != 0}
fossil wiki create "Unstamped Note" f10 --technote now
test wiki-34 {$CODE == 0}
fossil wiki list -t 
test wiki-35 {[string match "*$t2*" $RESULT]}

###############################################################################
# Check an attachment to it in the same second works. 
write_file f11 "Time Stamp was $t2"
fossil attachment add f11 --technote $t2
test wiki-36 {$CODE == 0}
fossil timeline
test wiki-36-1 {$CODE == 0}
fossil wiki list -t
test wiki-36-2 {$CODE == 0}

###############################################################################
# Check that we have the expected number of tech notes on the list (and not 
# extra ones from other events (such as the attachments) - 8 tech notes 
# expected created by tests 9, 13, 17, 19, 29, 31, 32 and 34 
fossil wiki list --technote
set technotelist [split $RESULT "\n"] 
test wiki-37 {[llength $technotelist] == 8}

###############################################################################
# Check that using the show-technote-ids shows the same tech notes in the same
# order (with the technote id as the first word of the line)
fossil wiki list --technote --show-technote-ids
set technoteidlist [split $RESULT "\n"]
................................................................................
set anoldtechnoteid [lindex [split [lindex $technotelist [llength $technotelist]-1]] 0]
fossil wiki export a12 --technote $anoldtechnoteid
test wiki-40 {[similar_file f12 a12]}

###############################################################################
# Also check that we can specify a prefix of the tech note id (note: with
# 9 items in the tech note at this point there is a chance of a collision.
# However with a 20 character prefix the chance of the collision is 
# approximately 1 in 10^22 so this test ignores that possibility.)
fossil wiki export a12.1 --technote [string range $anoldtechnoteid 0 20]
test wiki-41 {[similar_file f12 a12.1]}

###############################################################################
# Now we need to force a collision in the first four characters of the tech
# note id if we don't already have one so we can check we get an error if the
................................................................................
set technotelist [split $RESULT "\n"]
for {set i 0} {$i < [llength $technotelist]} {incr i} {
  set fullid [lindex $technotelist $i]
  set id [string range $fullid 0 3]
  dict incr idcounts $id
  if {[dict get $idcounts $id] > $maxcount} {
    set maxid $id
    incr maxcount 
  }
}
# get i so that, as a julian date, it is in the 1800s, i.e., older than
# any other tech note, but after 1 AD
set i 2400000
while {$maxcount < 2} {
  # keep getting older
................................................................................
  fossil wiki list --technote --show-technote-ids
  set technotelist [split $RESULT "\n"]
  set oldesttechnoteid [lindex [split [lindex $technotelist [llength $technotelist]-1]] 0]
  set id [string range $oldesttechnoteid 0 3]
  dict incr idcounts $id
  if {[dict get $idcounts $id] > $maxcount} {
    set maxid $id
    incr maxcount 
  }
}
# Save the duplicate id for this and later tests
set duplicateid $maxid
fossil wiki export a13 --technote $duplicateid -expectError
test wiki-42 {$CODE != 0}








|







 







|







 







|
|



|







 







|



|









|
|
|

|







 







|







 







|







 







|







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
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
266
267
268
269
270
271
272
...
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

###############################################################################
# Trying to add a technote with the same timestamp should succeed and create a
# second tech note
fossil wiki create 2ndnote f3 -technote {2016-01-01 12:34}
test wiki-13 {$CODE == 0}
fossil wiki list --technote
set technotelist [split $RESULT "\n"]
test wiki-13.1 {[llength $technotelist] == 2}

###############################################################################
# commiting a change to an existing technote should replace the page on export
# (this should update the tech note from wiki-13 as that the most recently
# updated one, that should also be the one exported by the export command)
write_file f4 "technote 2nd variant"
................................................................................

###############################################################################
# But we shouldn't be able to update non-existant pages
fossil wiki commit doesntexist f1 -expectError
test wiki-16 {$CODE != 0}

###############################################################################
# Check specifying tags for a technote is OK
write_file f5 "technote with tags"
fossil wiki create {tagged technote} f5 --technote {2016-01-02 12:34} --technote-tags {A B}
test wiki-17 {$CODE == 0}
write_file f5.1 "editted and tagged technote"
fossil wiki commit {tagged technote} f5 --technote {2016-01-02 12:34} --technote-tags {C D}
test wiki-18 {$CODE == 0}

................................................................................
write_file f8 "Contents of a 'unique' tech note"
fossil wiki create {Unique technote} f8 --technote {2016-01-05 01:02:03}
fossil timeline
test wiki-30 {[string match *Unique*technote* $RESULT]}

###############################################################################
# Check for a collision between an attachment and a note, this was a
# bug that resulted from some code treating the attachment entry as if it
# were a technote when it isn't really.
#
# First, wait for the top of the next second so the attachment
# happens at a known time, then add an attachment to an existing note
# and a new note immediately after.

set t0 [clock seconds]
while {$t0 == [clock seconds]} {
  after 100
}
set t1 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
write_file f9 "Timestamp: $t1"
................................................................................
# "now" is a valid stamp.
set t2 [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d %H:%M:%S"]
write_file f10 "Even unstampted notes are delivered.\nStamped $t2"
fossil wiki create "Unstamped Note" f10 --technote -expectError
test wiki-33 {$CODE != 0}
fossil wiki create "Unstamped Note" f10 --technote now
test wiki-34 {$CODE == 0}
fossil wiki list -t
test wiki-35 {[string match "*$t2*" $RESULT]}

###############################################################################
# Check an attachment to it in the same second works.
write_file f11 "Time Stamp was $t2"
fossil attachment add f11 --technote $t2
test wiki-36 {$CODE == 0}
fossil timeline
test wiki-36-1 {$CODE == 0}
fossil wiki list -t
test wiki-36-2 {$CODE == 0}

###############################################################################
# Check that we have the expected number of tech notes on the list (and not
# extra ones from other events (such as the attachments) - 8 tech notes
# expected created by tests 9, 13, 17, 19, 29, 31, 32 and 34
fossil wiki list --technote
set technotelist [split $RESULT "\n"]
test wiki-37 {[llength $technotelist] == 8}

###############################################################################
# Check that using the show-technote-ids shows the same tech notes in the same
# order (with the technote id as the first word of the line)
fossil wiki list --technote --show-technote-ids
set technoteidlist [split $RESULT "\n"]
................................................................................
set anoldtechnoteid [lindex [split [lindex $technotelist [llength $technotelist]-1]] 0]
fossil wiki export a12 --technote $anoldtechnoteid
test wiki-40 {[similar_file f12 a12]}

###############################################################################
# Also check that we can specify a prefix of the tech note id (note: with
# 9 items in the tech note at this point there is a chance of a collision.
# However with a 20 character prefix the chance of the collision is
# approximately 1 in 10^22 so this test ignores that possibility.)
fossil wiki export a12.1 --technote [string range $anoldtechnoteid 0 20]
test wiki-41 {[similar_file f12 a12.1]}

###############################################################################
# Now we need to force a collision in the first four characters of the tech
# note id if we don't already have one so we can check we get an error if the
................................................................................
set technotelist [split $RESULT "\n"]
for {set i 0} {$i < [llength $technotelist]} {incr i} {
  set fullid [lindex $technotelist $i]
  set id [string range $fullid 0 3]
  dict incr idcounts $id
  if {[dict get $idcounts $id] > $maxcount} {
    set maxid $id
    incr maxcount
  }
}
# get i so that, as a julian date, it is in the 1800s, i.e., older than
# any other tech note, but after 1 AD
set i 2400000
while {$maxcount < 2} {
  # keep getting older
................................................................................
  fossil wiki list --technote --show-technote-ids
  set technotelist [split $RESULT "\n"]
  set oldesttechnoteid [lindex [split [lindex $technotelist [llength $technotelist]-1]] 0]
  set id [string range $oldesttechnoteid 0 3]
  dict incr idcounts $id
  if {[dict get $idcounts $id] > $maxcount} {
    set maxid $id
    incr maxcount
  }
}
# Save the duplicate id for this and later tests
set duplicateid $maxid
fossil wiki export a13 --technote $duplicateid -expectError
test wiki-42 {$CODE != 0}

Changes to www/mkindex.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
#
# Run this TCL script to generate a WIKI page that contains a
# permuted index of the various documentation files.
#
#    tclsh mkindex.tcl 
#

set doclist {
  aboutcgi.wiki {How CGI Works In Fossil}
  adding_code.wiki {Adding New Features To Fossil}
  adding_code.wiki {Hacking Fossil}
  antibot.wiki {Defense against Spiders and Bots}





|







1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
#
# Run this TCL script to generate a WIKI page that contains a
# permuted index of the various documentation files.
#
#    tclsh mkindex.tcl
#

set doclist {
  aboutcgi.wiki {How CGI Works In Fossil}
  adding_code.wiki {Adding New Features To Fossil}
  adding_code.wiki {Hacking Fossil}
  antibot.wiki {Defense against Spiders and Bots}

Changes to www/unvers.wiki.

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

The [/help?cmd=sync|fossil sync] and [/help?cmd=clone|fossil clone]
commands will synchronize unversioned content if and only if the
"-u" (or "--unversioned") command-line option is supplied.  The
[/help?cmd=unversioned|fossil unversioned sync] command will synchronize the
unversioned content without synchronizing anything else.

Notice that the "-u" option does not work on 
[/help?cmd=push|fossil push] or [/help?cmd=pull|fossil pull].
The "-u" option is only available on "sync" and "clone".

A rough equivalent of an unversioned pull would be the
[/help?cmd=unversioned|fossil unversioned revert] command.  The 
"unversioned revert"
command causes the unversioned content on the local repository to overwritten
by the unversioned content found on the remote repository.

<h2>Implementation Details</h2>

<i>(This section outlines the current implementation of unversioned
files.  This is not an interface spec and hence subject to change.)</i>

Unversioned content is stored in the repository in the 
"unversioned" table:

<blockquote><pre>
CREATE TABLE unversioned(
  name TEXT PRIMARY KEY,  -- Name of the file
  rcvid INTEGER,          -- From whence this file was received
  mtime DATETIME,         -- Last change (seconds since 1970)







|




|









|







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

The [/help?cmd=sync|fossil sync] and [/help?cmd=clone|fossil clone]
commands will synchronize unversioned content if and only if the
"-u" (or "--unversioned") command-line option is supplied.  The
[/help?cmd=unversioned|fossil unversioned sync] command will synchronize the
unversioned content without synchronizing anything else.

Notice that the "-u" option does not work on
[/help?cmd=push|fossil push] or [/help?cmd=pull|fossil pull].
The "-u" option is only available on "sync" and "clone".

A rough equivalent of an unversioned pull would be the
[/help?cmd=unversioned|fossil unversioned revert] command.  The
"unversioned revert"
command causes the unversioned content on the local repository to overwritten
by the unversioned content found on the remote repository.

<h2>Implementation Details</h2>

<i>(This section outlines the current implementation of unversioned
files.  This is not an interface spec and hence subject to change.)</i>

Unversioned content is stored in the repository in the
"unversioned" table:

<blockquote><pre>
CREATE TABLE unversioned(
  name TEXT PRIMARY KEY,  -- Name of the file
  rcvid INTEGER,          -- From whence this file was received
  mtime DATETIME,         -- Last change (seconds since 1970)

Changes to www/webui.wiki.

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  *  Historical change data
  *  Add and remove tags on check-ins
  *  Move check-ins between branches
  *  Revise check-in comments
  *  Manage user credentials and access permissions
  *  And so forth... (some [./webpage-ex.md|examples])

You get all of this, and more, for free when you use Fossil. 
There are no extra programs to install or setup.
Everything you need is already pre-configured and built into the
self-contained, stand-alone Fossil executable.

As an example of how useful this web interface can be,
the entire [./index.wiki | Fossil website] (except for the
[http://www.fossil-scm.org/download.html | download page]),
................................................................................
To start using the built-in Fossil web interface on an existing Fossil
repository, simply type this:

   <b>fossil ui existing-repository.fossil</b>

Substitute the name of your repository, of course.
The "ui" command will start a webserver running (it figures out an
available TCP port to use on its own) and then automatically launches 
your web browser to point at that server.  If you run the "ui" command
from within an open check-out, you can omit the repository name:

  <b>fossil ui</b>

The latter case is a very useful short-cut when you are working on a
Fossil project and you want to quickly do some work with the web interface.
................................................................................
  #!/usr/local/bin/fossil
  repository: /home/www/sample-project.fossil
  </verbatim>

Adjust the script above so that the paths are correct for your system,
of course, and also make sure the Fossil binary is installed on the server.
But that is <u>all</u> you have to do.  You now have everything you need to host
a distributed software development project in less than five minutes using a 
two-line CGI script.

Instructions for setting up an SCGI server are
[./scgi.wiki | available separately].

You don't have a CGI- or SCGI-capable web server running on your
server machine?
Not a problem.  The Fossil interface can also be launched via inetd or
xinetd.  An inetd configuration line sufficient to launch the Fossil
web interface looks like this:

  <verbatim>
  80 stream tcp nowait.1000 root /usr/local/bin/fossil \
  /usr/local/bin/fossil http /home/www/sample-project.fossil 
  </verbatim>

As always, you'll want to adjust the pathnames to whatever is appropriate
for your system.  The xinetd setup uses a different syntax but follows
the same idea.







|







 







|







 







|













|





17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  *  Historical change data
  *  Add and remove tags on check-ins
  *  Move check-ins between branches
  *  Revise check-in comments
  *  Manage user credentials and access permissions
  *  And so forth... (some [./webpage-ex.md|examples])

You get all of this, and more, for free when you use Fossil.
There are no extra programs to install or setup.
Everything you need is already pre-configured and built into the
self-contained, stand-alone Fossil executable.

As an example of how useful this web interface can be,
the entire [./index.wiki | Fossil website] (except for the
[http://www.fossil-scm.org/download.html | download page]),
................................................................................
To start using the built-in Fossil web interface on an existing Fossil
repository, simply type this:

   <b>fossil ui existing-repository.fossil</b>

Substitute the name of your repository, of course.
The "ui" command will start a webserver running (it figures out an
available TCP port to use on its own) and then automatically launches
your web browser to point at that server.  If you run the "ui" command
from within an open check-out, you can omit the repository name:

  <b>fossil ui</b>

The latter case is a very useful short-cut when you are working on a
Fossil project and you want to quickly do some work with the web interface.
................................................................................
  #!/usr/local/bin/fossil
  repository: /home/www/sample-project.fossil
  </verbatim>

Adjust the script above so that the paths are correct for your system,
of course, and also make sure the Fossil binary is installed on the server.
But that is <u>all</u> you have to do.  You now have everything you need to host
a distributed software development project in less than five minutes using a
two-line CGI script.

Instructions for setting up an SCGI server are
[./scgi.wiki | available separately].

You don't have a CGI- or SCGI-capable web server running on your
server machine?
Not a problem.  The Fossil interface can also be launched via inetd or
xinetd.  An inetd configuration line sufficient to launch the Fossil
web interface looks like this:

  <verbatim>
  80 stream tcp nowait.1000 root /usr/local/bin/fossil \
  /usr/local/bin/fossil http /home/www/sample-project.fossil
  </verbatim>

As always, you'll want to adjust the pathnames to whatever is appropriate
for your system.  The xinetd setup uses a different syntax but follows
the same idea.