Fossil

Check-in [573a464cb7]
Login

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

Overview
Comment:Complete rework of the xfer mechanism. Compiles but not yet working.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 573a464cb7be4d45ee10e206ec6be775f47b2323
User & Date: drh 2007-08-10 00:08:25.000
Context
2007-08-10
02:59
The xfer mechanism has been completely reworked to better support delta compression and to require fewer round-trips. The wire protocol is roughly the same but is different enough that you will need to recompile before sync will work. check-in: edbb332d54 user: drh tags: trunk
00:08
Complete rework of the xfer mechanism. Compiles but not yet working. check-in: 573a464cb7 user: drh tags: trunk
2007-08-09
19:07
Additional work on the xfer mechanism, trying to increase the use of delta compression. check-in: bd3c1d0023 user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/checkin.c.
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

    id = db_column_int(&q, 0);
    zFullname = db_column_text(&q, 1);
    rid = db_column_int(&q, 2);

    blob_zero(&content);
    blob_read_from_file(&content, zFullname);
    nrid = content_put(&content, 0);
    if( rid>0 ){
      content_deltify(rid, nrid, 0);
    }
    db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id);
  }
  db_finalize(&q);








|







348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

    id = db_column_int(&q, 0);
    zFullname = db_column_text(&q, 1);
    rid = db_column_int(&q, 2);

    blob_zero(&content);
    blob_read_from_file(&content, zFullname);
    nrid = content_put(&content, 0, 0);
    if( rid>0 ){
      content_deltify(rid, nrid, 0);
    }
    db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id);
  }
  db_finalize(&q);

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
      exit(1);
    }
  }
  blob_write_to_file(&manifest, zManifestFile);
  blob_reset(&manifest);
  blob_read_from_file(&manifest, zManifestFile);
  free(zManifestFile);
  nvid = content_put(&manifest, 0);
  if( nvid==0 ){
    fossil_panic("trouble committing manifest: %s", g.zErrMsg);
  }
  manifest_crosslink(nvid, &manifest);
  content_deltify(vid, nvid, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
  printf("New_Version: %s\n", zUuid);







|







406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
      exit(1);
    }
  }
  blob_write_to_file(&manifest, zManifestFile);
  blob_reset(&manifest);
  blob_read_from_file(&manifest, zManifestFile);
  free(zManifestFile);
  nvid = content_put(&manifest, 0, 0);
  if( nvid==0 ){
    fossil_panic("trouble committing manifest: %s", g.zErrMsg);
  }
  manifest_crosslink(nvid, &manifest);
  content_deltify(vid, nvid, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
  printf("New_Version: %s\n", zUuid);
Changes to src/content.c.
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
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
100
101
102
103
#include "content.h"
#include <assert.h>

/*
** Return the srcid associated with rid.  Or return 0 if rid is 
** original content and not a delta.
*/
static int findSrcid(int rid, const char *zDb){
  Stmt qsrc;
  int srcid;
  if( zDb ){
    db_prepare(&qsrc, "SELECT srcid FROM %s.delta WHERE rid=%d", zDb, rid);
  }else{
    db_prepare(&qsrc, "SELECT srcid FROM delta WHERE rid=%d", rid);
  }
  if( db_step(&qsrc)==SQLITE_ROW ){
    srcid = db_column_int(&qsrc, 0);
  }else{
    srcid = 0;
  }
  db_finalize(&qsrc);
  return srcid;
}

/*
** Extract the content for ID rid and put it into the
** uninitialized blob.

*/
void content_get_from_db(int rid, Blob *pBlob, const char *zDb){
  Stmt q;

  int srcid;


  assert( g.repositoryOpen );
  srcid = findSrcid(rid, zDb);
  if( zDb ){
    db_prepare(&q, 
       "SELECT content FROM %s.blob WHERE rid=%d AND size>=0",
       zDb, rid
    );
  }else{
    db_prepare(&q, 
       "SELECT content FROM blob WHERE rid=%d AND size>=0",
       rid
    );
  }
  if( srcid ){
    Blob src;
    content_get_from_db(srcid, &src, zDb);

    if( db_step(&q)==SQLITE_ROW ){
      Blob delta;
      db_ephemeral_blob(&q, 0, &delta);
      blob_uncompress(&delta, &delta);
      blob_init(pBlob,0,0);
      blob_delta_apply(&src, &delta, pBlob);
      blob_reset(&delta);
    }else{
      blob_init(pBlob, 0, 0);

    }

    blob_reset(&src);

  }else{

    if( db_step(&q)==SQLITE_ROW ){
      db_ephemeral_blob(&q, 0, pBlob);
      blob_uncompress(pBlob, pBlob);
    }else{
      blob_init(pBlob,0, 0);

    }
  }
  db_finalize(&q);
}
void content_get(int rid, Blob *pBlob){
  content_get_from_db(rid, pBlob, 0);

}

/*
** COMMAND:  test-content-get
**
** Extract a blob from the database and write it into a file.
*/







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





|
>

|

>

>
>

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

<
|
>
|
|
|
|
|
|
|
<
<
>
|
>
|
>

>



<
<
>

<
|
|
<
<
>







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


63
64
65
66
67
68
69
70
71
72


73
74

75
76


77
78
79
80
81
82
83
84
#include "content.h"
#include <assert.h>

/*
** Return the srcid associated with rid.  Or return 0 if rid is 
** original content and not a delta.
*/
static int findSrcid(int rid){





  int srcid = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);







  return srcid;
}

/*
** Extract the content for ID rid and put it into the
** uninitialized blob.  Return 1 on success.  If the record
** is a phantom, zero pBlob and return 0.
*/
int content_get(int rid, Blob *pBlob){
  Stmt q;
  Blob src;
  int srcid;
  int rc = 0;

  assert( g.repositoryOpen );
  srcid = findSrcid(rid);




  blob_zero(pBlob);






  if( srcid ){

    if( content_get(srcid, &src) ){
      db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
      if( db_step(&q)==SQLITE_ROW ){
        Blob delta;
        db_ephemeral_blob(&q, 0, &delta);
        blob_uncompress(&delta, &delta);
        blob_init(pBlob,0,0);
        blob_delta_apply(&src, &delta, pBlob);
        blob_reset(&delta);


        rc = 1;
      }
      db_finalize(&q);
      blob_reset(&src);
    }
  }else{
    db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
    if( db_step(&q)==SQLITE_ROW ){
      db_ephemeral_blob(&q, 0, pBlob);
      blob_uncompress(pBlob, pBlob);


      rc = 1;
    }

    db_finalize(&q);
  }


  return rc;
}

/*
** COMMAND:  test-content-get
**
** Extract a blob from the database and write it into a file.
*/
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
  db_must_be_within_tree();
  rid = name_to_rid(g.argv[2]);
  blob_zero(&content);
  db_blob(&content, "SELECT content FROM blob WHERE rid=%d", rid);
  blob_uncompress(&content, &content);
  blob_write_to_file(&content, zFile);
}






















/*
** Write content into the database.  Return the record ID.  If the
** content is already in the database, just return the record ID.
**



** A phantom is written if pBlob==0.  If pBlob==0 then the UUID is set
** to zUuid.  Otherwise zUuid is ignored.


**
** If the record already exists but is a phantom, the pBlob content
** is inserted and the phatom becomes a real record.
*/
int content_put(Blob *pBlob, const char *zUuid){
  int size;
  int rid;
  Stmt s1;
  Blob cmpr;
  Blob hash;
  assert( g.repositoryOpen );
  if( pBlob==0 ){


    blob_init(&hash, zUuid, -1);


    size = -1;
  }else{
    sha1sum_blob(pBlob, &hash);
    size = blob_size(pBlob);
  }
  db_begin_transaction();

  /* Check to see if the entry already exists and if it does whether
  ** or not the entry is a phantom
  */







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





>
>
>
|
|
>
>




|






|
>
>

>
>


<







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
174
  db_must_be_within_tree();
  rid = name_to_rid(g.argv[2]);
  blob_zero(&content);
  db_blob(&content, "SELECT content FROM blob WHERE rid=%d", rid);
  blob_uncompress(&content, &content);
  blob_write_to_file(&content, zFile);
}

/*
** When a record is converted from a phantom to a real record,
** if that record has other records that are derived by delta,
** then call manifest_crosslink() on those other records.
*/
void after_dephantomize(int rid, int linkFlag){
  Stmt q;
  db_prepare(&q, "SELECT rid FROM delta WHERE srcid=%d", rid);
  while( db_step(&q)==SQLITE_ROW ){
    int tid = db_column_int(&q, 0);
    after_dephantomize(tid, 1);
  }
  db_finalize(&q);
  if( linkFlag ){
    Blob content;
    content_get(rid, &content);
    manifest_crosslink(rid, &content);
    blob_reset(&content);
  }
}

/*
** Write content into the database.  Return the record ID.  If the
** content is already in the database, just return the record ID.
**
** If srcId is specified, then pBlob is delta content from
** the srcId record.  srcId might be a phantom.
**
** A phantom is written if pBlob==0.  If pBlob==0 or if srcId is
** specified then the UUID is set to zUuid.  Otherwise zUuid is
** ignored.  In the future this might change such that the content
** hash is checked against zUuid to make sure it is correct.
**
** If the record already exists but is a phantom, the pBlob content
** is inserted and the phatom becomes a real record.
*/
int content_put(Blob *pBlob, const char *zUuid, int srcId){
  int size;
  int rid;
  Stmt s1;
  Blob cmpr;
  Blob hash;
  assert( g.repositoryOpen );
  if( pBlob && srcId==0 ){
    sha1sum_blob(pBlob, &hash);
  }else{
    blob_init(&hash, zUuid, -1);
  }
  if( pBlob==0 ){
    size = -1;
  }else{

    size = blob_size(pBlob);
  }
  db_begin_transaction();

  /* Check to see if the entry already exists and if it does whether
  ** or not the entry is a phantom
  */
195
196
197
198
199
200
201



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219






220
221
222
223
224
225
226
      "UPDATE blob SET rcvid=%d, size=%d, content=:data WHERE rid=%d",
       g.rcvid, size, rid
    );
    blob_compress(pBlob, &cmpr);
    db_bind_blob(&s1, ":data", &cmpr);
    db_exec(&s1);
    db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);



  }else{
    /* We are creating a new entry */
    db_prepare(&s1,
      "INSERT INTO blob(rcvid,size,uuid,content)"
      "VALUES(%d,%d,'%s',:data)",
       g.rcvid, size, blob_str(&hash)
    );
    if( pBlob ){
      blob_compress(pBlob, &cmpr);
      db_bind_blob(&s1, ":data", &cmpr);
    }
    db_exec(&s1);
    rid = db_last_insert_rowid();
    if( !pBlob ){
      db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
    }
  }








  /* Finish the transaction and cleanup */
  db_finalize(&s1);
  db_end_transaction(0);
  blob_reset(&hash);

  /* Make arrangements to verify that the data can be recovered







>
>
>




|
|












>
>
>
>
>
>







205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
      "UPDATE blob SET rcvid=%d, size=%d, content=:data WHERE rid=%d",
       g.rcvid, size, rid
    );
    blob_compress(pBlob, &cmpr);
    db_bind_blob(&s1, ":data", &cmpr);
    db_exec(&s1);
    db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
    if( srcId==0 || db_int(0, "SELECT size FROM blob WHERE rid=%d", srcId)>0 ){
      after_dephantomize(rid, 0);
    }
  }else{
    /* We are creating a new entry */
    db_prepare(&s1,
      "INSERT INTO blob(rcvid,size,uuid,content)"
      "VALUES(%d,%d,'%b',:data)",
       g.rcvid, size, &hash
    );
    if( pBlob ){
      blob_compress(pBlob, &cmpr);
      db_bind_blob(&s1, ":data", &cmpr);
    }
    db_exec(&s1);
    rid = db_last_insert_rowid();
    if( !pBlob ){
      db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
    }
  }

  /* If the srcId is specified, then the data we just added is
  ** really a delta.  Record this fact in the delta table.
  */
  if( srcId ){
    db_multi_exec("REPLACE INTO delta(rid,srcid) VALUES(%d,%d)", rid, srcId);
  }

  /* Finish the transaction and cleanup */
  db_finalize(&s1);
  db_end_transaction(0);
  blob_reset(&hash);

  /* Make arrangements to verify that the data can be recovered
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
void test_content_put_cmd(void){
  int rid;
  Blob content;
  if( g.argc!=3 ) usage("FILENAME");
  db_must_be_within_tree();
  user_select();
  blob_read_from_file(&content, g.argv[2]);
  rid = content_put(&content, 0);
  printf("inserted as record %d\n", rid);
}

/*
** Make sure the content at rid is the original content and is not a
** delta.
*/
void content_undelta(int rid){
  if( findSrcid(rid, 0)>0 ){
    Blob x;

    Stmt s;
    content_get(rid, &x);
    db_prepare(&s, "UPDATE blob SET content=:c WHERE rid=%d", rid);

    blob_compress(&x, &x);
    db_bind_blob(&s, ":c", &x);
    db_exec(&s);
    db_finalize(&s);
    blob_reset(&x);
    db_multi_exec("DELETE FROM delta WHERE rid=%d", rid);

  }
}

/*
** COMMAND:  test-content-undelta
**
** Make sure the content at RECORDID is not a delta







|








|

>
|
<
|
>
|
|
|
|
|
|
>







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
void test_content_put_cmd(void){
  int rid;
  Blob content;
  if( g.argc!=3 ) usage("FILENAME");
  db_must_be_within_tree();
  user_select();
  blob_read_from_file(&content, g.argv[2]);
  rid = content_put(&content, 0, 0);
  printf("inserted as record %d\n", rid);
}

/*
** Make sure the content at rid is the original content and is not a
** delta.
*/
void content_undelta(int rid){
  if( findSrcid(rid)>0 ){
    Blob x;
    if( content_get(rid, &x) ){
      Stmt s;

      db_prepare(&s, "UPDATE blob SET content=:c, size=%d WHERE rid=%d",
                     blob_size(&x), rid);
      blob_compress(&x, &x);
      db_bind_blob(&s, ":c", &x);
      db_exec(&s);
      db_finalize(&s);
      blob_reset(&x);
      db_multi_exec("DELETE FROM delta WHERE rid=%d", rid);
    }
  }
}

/*
** COMMAND:  test-content-undelta
**
** Make sure the content at RECORDID is not a delta
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
** converted to undeltaed text.
*/
void content_deltify(int rid, int srcid, int force){
  int s;
  Blob data, src, delta;
  Stmt s1, s2;
  if( srcid==rid ) return;
  if( !force && findSrcid(rid, 0)>0 ) return;
  s = srcid;
  while( (s = findSrcid(s, 0))>0 ){
    if( s==rid ){
      content_undelta(srcid);
      break;
    }
  }
  content_get(srcid, &src);
  content_get(rid, &data);







|

|







311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
** converted to undeltaed text.
*/
void content_deltify(int rid, int srcid, int force){
  int s;
  Blob data, src, delta;
  Stmt s1, s2;
  if( srcid==rid ) return;
  if( !force && findSrcid(rid)>0 ) return;
  s = srcid;
  while( (s = findSrcid(s))>0 ){
    if( s==rid ){
      content_undelta(srcid);
      break;
    }
  }
  content_get(srcid, &src);
  content_get(rid, &data);
Changes to src/db.c.
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
  blob_appendf(&manifest, "P\n");
  md5sum_init();
  blob_appendf(&manifest, "R %s\n", md5sum_finish(0));
  blob_appendf(&manifest, "U %F\n", g.zLogin);
  md5sum_blob(&manifest, &hash);
  blob_appendf(&manifest, "Z %b\n", &hash);
  blob_reset(&hash);
  content_put(&manifest, 0);
  db_end_transaction(0);
  printf("project-id: %s\n", db_get("project-code", 0));
  printf("server-id:  %s\n", db_get("server-code", 0));
  printf("admin-user: %s (no password set yet!)\n", g.zLogin);
  printf("baseline:   %s\n", db_text(0, "SELECT uuid FROM blob"));
}








|







697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
  blob_appendf(&manifest, "P\n");
  md5sum_init();
  blob_appendf(&manifest, "R %s\n", md5sum_finish(0));
  blob_appendf(&manifest, "U %F\n", g.zLogin);
  md5sum_blob(&manifest, &hash);
  blob_appendf(&manifest, "Z %b\n", &hash);
  blob_reset(&hash);
  content_put(&manifest, 0, 0);
  db_end_transaction(0);
  printf("project-id: %s\n", db_get("project-code", 0));
  printf("server-id:  %s\n", db_get("server-code", 0));
  printf("admin-user: %s (no password set yet!)\n", g.zLogin);
  printf("baseline:   %s\n", db_text(0, "SELECT uuid FROM blob"));
}

815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
}
int db_lget_int(const char *zName, int dflt){
  return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);
}
void db_lset_int(const char *zName, int value){
  db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%d)", zName, value);
}

int db_row_to_table(const char *zFormat, ...){
  Stmt q;
  va_list ap;
  int rc;

  va_start(ap, zFormat);
  rc = db_vprepare(&q, zFormat, ap);
  va_end(ap);
  if( rc!=SQLITE_OK ){
    return rc;
  }

  @ <table border="0" cellpadding="0" cellspacing="0">
  if( db_step(&q)==SQLITE_ROW ){
    int ii;
    for(ii=0; ii<sqlite3_column_count(q.pStmt); ii++){
      char *zCol = htmlize(sqlite3_column_name(q.pStmt, ii), -1);
      char *zVal = htmlize(sqlite3_column_text(q.pStmt, ii), -1);

      @ <tr><td align=right>%s(zCol):<td width=10><td>%s(zVal)

      free(zVal);
      free(zCol);
    }
  }
  @ </table>

  return db_finalize(&q);
}


/*
** COMMAND: open
**
** Create a new local repository.
*/
void cmd_open(void){







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







815
816
817
818
819
820
821































822
823
824
825
826
827
828
}
int db_lget_int(const char *zName, int dflt){
  return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);
}
void db_lset_int(const char *zName, int value){
  db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%d)", zName, value);
}
































/*
** COMMAND: open
**
** Create a new local repository.
*/
void cmd_open(void){
Changes to src/manifest.c.
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
    j++;
  }
  manifest_clear(&other);
}

/*
** Scan record rid/pContent to see if it is a manifest.  If
** it is a manifest, then populate tables the mlink, plink,
** filename, and event tables with cross-reference information.
*/
int manifest_crosslink(int rid, Blob *pContent){
  int i;
  Manifest m;
  Stmt q;








|







287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
    j++;
  }
  manifest_clear(&other);
}

/*
** Scan record rid/pContent to see if it is a manifest.  If
** it is a manifest, then populate the mlink, plink,
** filename, and event tables with cross-reference information.
*/
int manifest_crosslink(int rid, Blob *pContent){
  int i;
  Manifest m;
  Stmt q;

Changes to src/vfile.c.
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  if( sz!=UUID_SIZE || !validate16(zUuid, sz) ){
    return 0;
  }
  strcpy(z, zUuid);
  canonical16(z, sz);
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", z);
  if( rid==0 && phantomize ){
    rid = content_put(0, zUuid);
  }
  return rid;
}

/*
** Build a catalog of all files in a baseline.
** We scan the baseline file for lines of the form:







|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  if( sz!=UUID_SIZE || !validate16(zUuid, sz) ){
    return 0;
  }
  strcpy(z, zUuid);
  canonical16(z, sz);
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", z);
  if( rid==0 && phantomize ){
    rid = content_put(0, zUuid, 0);
  }
  return rid;
}

/*
** Build a catalog of all files in a baseline.
** We scan the baseline file for lines of the form:
Changes to src/xfer.c.
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
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
100


101
102
103
104
105
106
107
108
109
110
111



112


113
114
115

116
117

118
119
120
121
122
123
124
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
174
175
176
177
178
179
180
181
182


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
212
213
214
215
216
217
218
219
220
221

222
223
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
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
**
** This file contains code to implement the file transfer protocol.
*/
#include "config.h"
#include "xfer.h"

/*

** Try to locate a record that is similar to rid and is a likely
** candidate for delta against rid.  The similar record must be
** referenced in the onremote table.
**
** Return the integer record ID of the similar record.  Or return
** 0 if none is found.
*/
static int similar_record(int rid, int traceFlag){
  int inCnt, outCnt;
  int i;
  Stmt q;
  int queue[100];
  static const char *azQuery[] = {
      /* Scan the delta table first */
      "SELECT srcid, EXISTS(SELECT 1 FROM onremote WHERE rid=srcid)"
      "  FROM delta"
      " WHERE rid=:x"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=srcid)"
      " UNION ALL "
      "SELECT rid, EXISTS(SELECT 1 FROM onremote WHERE rid=delta.rid)"
      "  FROM delta"
      " WHERE srcid=:x"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=delta.rid)",


      /* Then the plink table */
      "SELECT pid, EXISTS(SELECT 1 FROM onremote WHERE rid=pid)"
      "  FROM plink"
      " WHERE cid=:x"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
      " UNION ALL "
      "SELECT cid, EXISTS(SELECT 1 FROM onremote WHERE rid=cid)"
      "  FROM plink"
      " WHERE pid=:x"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=cid)",

      /* Finally the mlink table */
      "SELECT pid, EXISTS(SELECT 1 FROM onremote WHERE rid=pid)"
      "  FROM mlink"
      " WHERE fid=:x AND pid>0"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
      " UNION ALL "
      "SELECT fid, EXISTS(SELECT 1 FROM onremote WHERE rid=fid)"
      "  FROM mlink"
      " WHERE pid=:x AND fid>0"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=fid)",
  };

  for(i=0; i<sizeof(azQuery)/sizeof(azQuery[0]); i++){
    db_prepare(&q, azQuery[i]);
    queue[0] = rid;
    inCnt = 1;
    outCnt = 0;
    if( traceFlag ) printf("PASS %d\n", i+1);
    while( outCnt<inCnt ){
      int xid = queue[outCnt%64];
      outCnt++;
      db_bind_int(&q, ":x", xid);
      if( traceFlag ) printf("xid=%d\n", xid);
      while( db_step(&q)==SQLITE_ROW ){
        int nid = db_column_int(&q, 0);
        int hit = db_column_int(&q, 1);
        if( traceFlag ) printf("nid=%d hit=%d\n", nid, hit);
        if( hit  ){
          db_finalize(&q);
          return nid;
        }
        if( inCnt<sizeof(queue)/sizeof(queue[0]) ){
          int i;
          for(i=0; i<inCnt && queue[i]!=nid; i++){}
          if( i>=inCnt ){
            queue[inCnt++] = nid;


          }
        }
      }
      db_reset(&q);
    }
    db_finalize(&q);
  }
  return 0;
}

/*



** COMMAND: test-similar-record


*/
void test_similar_record(void){
  int i;

  if( g.argc<4 ){
    usage("SRC ONREMOTE...");

  }
  db_must_be_within_tree();
  db_multi_exec(
    "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
  );
  for(i=3; i<g.argc; i++){
    int rid = name_to_rid(g.argv[i]);
    printf("%s -> %d\n", g.argv[i], rid);
    db_multi_exec("INSERT INTO onremote VALUES(%d)", rid);
  }
  printf("similar: %d\n", similar_record(name_to_rid(g.argv[2]), 1));
}


/*
** The aToken[0..nToken-1] blob array is a parse of a "file" line 
** message.  This routine finishes parsing that message and does
** a record insert of the file.
**
** The file line is in one of the following two forms:
**
**      file UUID SIZE \n CONTENT
**      file UUID DELTASRC SIZE \n CONTENT
**
** The content is SIZE bytes immediately following the newline.
** If DELTASRC exists, then the CONTENT is a delta against the
** content of DELTASRC.
**
** If any error occurs, write a message into pErr which has already
** be initialized to an empty string.
*/
static void xfer_accept_file(Blob *pIn, Blob *aToken, int nToken, Blob *pErr){
  int n;
  int rid;
  Blob content, hash;
  


  if( nToken<3 || nToken>4 || !blob_is_uuid(&aToken[1])
       || !blob_is_int(&aToken[nToken-1], &n) || n<=0

       || (nToken==4 && !blob_is_uuid(&aToken[2])) ){

    blob_appendf(pErr, "malformed file line");
    return;
  }
  blob_zero(&content);
  blob_zero(&hash);
  blob_extract(pIn, n, &content);
  if( nToken==4 ){
    Blob src;
    int srcid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &aToken[2]);
    if( srcid==0 ){

      blob_appendf(pErr, "unknown delta source: %b", &aToken[2]);

      return;
    }
    content_get(srcid, &src);

    blob_delta_apply(&src, &content, &content);
    blob_reset(&src);


  }
  sha1sum_blob(&content, &hash);
  if( !blob_eq_str(&aToken[1], blob_str(&hash), -1) ){
    blob_appendf(pErr, "content does not match sha1 hash");
  }
  blob_reset(&hash);
  rid = content_put(&content, 0);
  manifest_crosslink(rid, &content);
  if( rid==0 ){
    blob_appendf(pErr, "%s", g.zErrMsg);


  }
































































}

/*
** Send the file identified by rid.
**
** If pOut is not NULL, then append the text of the send message
** to pOut.  Otherwise, append the text to the CGI output.
*/
static int send_file(int rid, Blob *pOut){
  Blob content, uuid;
  int size;
  int srcid;




  blob_zero(&uuid);

  db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
  if( blob_size(&uuid)==0 ){
    return 0;








  }
  content_get(rid, &content);

  if( blob_size(&content)>100 ){
    srcid = similar_record(rid, 0);
    if( srcid ){
      Blob src;
      content_get(srcid, &src);
      if( blob_size(&src)>100 ){
        Blob delta;
        blob_delta_create(&src, &content, &delta);
        blob_reset(&content);
        content = delta;
        blob_append(&uuid, " ", 1);
        blob_append(&content, "\n", 1);
        db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d", srcid);
      }
      blob_reset(&src);
    }
  }

  size = blob_size(&content);
  if( pOut ){
    blob_appendf(pOut, "file %b %d\n", &uuid, size);
    blob_append(pOut, blob_buffer(&content), size);

  }else{



    cgi_printf("file %b %d\n", &uuid, size);
    cgi_append_content(blob_buffer(&content), size);

  }
  blob_reset(&content);
  blob_reset(&uuid);
  db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid);
  return size;
}


/*
** Send all pending files.



*/
static int send_all_pending(Blob *pOut){
  int rid, xid, i;
  int nIgot = 0;
  int sent = 0;
  int nSent = 0;
  int maxSize = db_get_int("http-msg-size", 500000);
  static const char *azQuery[] = {

      "SELECT srcid FROM delta JOIN pending ON pending.rid=delta.srcid"
      " WHERE delta.rid=%d"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=srcid)",










      "SELECT delta.rid FROM delta JOIN pending ON pending.rid=delta.rid"
      " WHERE srcid=%d"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=delta.rid)",



      "SELECT pid FROM plink JOIN pending ON rid=pid"
      " WHERE cid=%d"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",






      "SELECT cid FROM plink JOIN pending ON rid=cid"
      " WHERE pid=%d"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=cid)",

      "SELECT pid FROM mlink JOIN pending ON rid=pid"
      " WHERE fid=%d"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",

      "SELECT fid FROM mlink JOIN pending ON rid=fid"
      " WHERE pid=%d"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=fid)",
  };

  rid = db_int(0, "SELECT rid FROM pending");
  while( rid && nIgot<200 ){
    db_multi_exec("DELETE FROM pending WHERE rid=%d", rid);
    if( sent<maxSize ){
      sent += send_file(rid, pOut);
      nSent++;



    }else{

      char *zUuid = db_text(0,
                      "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
      if( zUuid ){

        if( pOut ){

          blob_appendf(pOut, "igot %s\n", zUuid);
        }else{
          cgi_printf("igot %s\n", zUuid);
        }
        free(zUuid);
        nIgot++;
      }
    }




    xid = 0;
    for(i=0; xid==0 && i<sizeof(azQuery)/sizeof(azQuery[0]); i++){

      xid = db_int(0, azQuery[i], rid);
    }
    rid = xid;
    if( rid==0 ){
      rid = db_int(0, "SELECT rid FROM pending");

    }
  }
  return nSent;

}


/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
**







>
|
<
<
<
<
<

<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
|
>
|
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
|
<
<
<
|
<
<
<
<
|
<
<
<
<
<
<
<
|
<
<
<
>
>
|
|
<
<
<
<
<
<
<


>
>
>
|
>
>

<
|
>
|
<
>

<
<
<
<
<
|
<
<
<
<




















|




>
>
|
|
>
|
>
|




|
|

|
|
>
|
>


<
>


>
>


|
|


|
<

|
>
>

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




<
<
<

|

|
<

>
>
|

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




<
<
<
<
<
<
<
<
<
<
|
<
|
<
<
<
>
|
<
|
|
>

>
>
>
|
<
>
|
<
<
<
<
<
<
<

<
>
>
>

<
|
<
|
<
<
<
>
|
|
|
>
>
>
>
>

>
>
>
>
|
<
|
|
>
>
|
|
<
>
>
>
|
>
>
|
<
<
|
<
<
<
|
<
<
<
<
|
<
<
<
<
<
|
>
>
>
|
>
|
|
<
>
|
>
|
<
|
|
|
<
|
|
>
>
>
>
|
<
>
|
<
|
<
<
>
|
<
<
>







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




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
100
101
102
103
104
105
106
107
108
109

110
111
112
113
114
115
116
117
118
119
120
121

122
123
124
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
174
175
176
177
178
179
180
181
182
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
212
213
214
215
216
217
218
219










220

221



222
223

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
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
**
** This file contains code to implement the file transfer protocol.
*/
#include "config.h"
#include "xfer.h"

/*
** This structure holds information about the current state of either
** a client or a server that is participating in xfer.





*/

typedef struct Xfer Xfer;
struct Xfer {













  Blob *pIn;          /* Input text from the other side */
  Blob *pOut;         /* Compose our reply here */
  Blob line;          /* The current line of input */









  Blob aToken[5];     /* Tokenized version of line */











  Blob err;           /* Error message text */



  int nToken;         /* Number of tokens in line */



  int nIGot;          /* Number of "igot" messages sent */




  int nFile;          /* Number of files sent or received */







  int nDelta;         /* Number of deltas sent or received */



  int nDanglingFile;  /* Number of dangling deltas received */
  int mxSend;         /* Stop sending "file" with pOut reaches this size */
};









/*
** The input blob contains a UUID.  Convert it into a record ID.
** Create a phantom record if no prior record exists and
** phantomize is true.
**
** Compare to uuid_to_rid().  This routine takes a blob argument
** and does less error checking.
*/

static int rid_from_uuid(Blob *pUuid, int phantomize){
  int rid = db_int(0, "SELECT rid FROM blob WHERE uuid='%b'", pUuid);
  if( rid==0 && phantomize ){

    rid = content_put(0, blob_str(pUuid), 0);
  }





  return rid;




}


/*
** The aToken[0..nToken-1] blob array is a parse of a "file" line 
** message.  This routine finishes parsing that message and does
** a record insert of the file.
**
** The file line is in one of the following two forms:
**
**      file UUID SIZE \n CONTENT
**      file UUID DELTASRC SIZE \n CONTENT
**
** The content is SIZE bytes immediately following the newline.
** If DELTASRC exists, then the CONTENT is a delta against the
** content of DELTASRC.
**
** If any error occurs, write a message into pErr which has already
** be initialized to an empty string.
*/
static void xfer_accept_file(Xfer *pXfer){
  int n;
  int rid;
  Blob content, hash;
  
  if( pXfer->nToken<3 
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<=0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed file line");
    return;
  }
  blob_zero(&content);
  blob_zero(&hash);
  blob_extract(pXfer->pIn, n, &content);
  if( pXfer->nToken==4 ){
    Blob src;
    int srcid = rid_from_uuid(&pXfer->aToken[2], 1);
    if( content_get(srcid, &src)==0 ){
      content_put(&content, blob_str(&hash), srcid);
      blob_appendf(pXfer->pOut, "gimme %b\n", &pXfer->aToken[2]);
      pXfer->nDanglingFile++;
      return;
    }

    pXfer->nDelta++;
    blob_delta_apply(&src, &content, &content);
    blob_reset(&src);
  }else{
    pXfer->nFile++;
  }
  sha1sum_blob(&content, &hash);
  if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){
    blob_appendf(&pXfer->err, "content does not match sha1 hash");
  }
  blob_reset(&hash);
  rid = content_put(&content, 0, 0);

  if( rid==0 ){
    blob_appendf(&pXfer->err, "%s", g.zErrMsg);
  }else{
    manifest_crosslink(rid, &content);
  }
}

/*
** Try to send a file as a delta.  If successful, return the number
** of bytes in the delta.  If not, return zero.
**
** If srcId is specified, use it.  If not, try to figure out a
** reasonable srcId.
*/
static int send_as_delta(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */
  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid,            /* The UUID of the file to send */
  int srcId               /* Send as a delta against this record */
){
  static const char *azQuery[] = {
    "SELECT srcid FROM delta JOIN pending ON pending.rid=delta.srcid"
    " WHERE delta.rid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=srcid)",

    "SELECT delta.rid FROM delta JOIN pending ON pending.rid=delta.rid"
    " WHERE srcid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=delta.rid)",

    "SELECT pid FROM plink JOIN pending ON rid=pid"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",

    "SELECT cid FROM plink JOIN pending ON rid=cid"
    " WHERE pid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=cid)",

    "SELECT pid FROM mlink JOIN pending ON rid=pid"
    " WHERE fid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",

    "SELECT fid FROM mlink JOIN pending ON rid=fid"
    " WHERE pid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=fid)",
  };
  int i;
  Blob src, delta;
  int size = 0;

  for(i=0; srcId==0 && i<count(azQuery); i++){
    srcId = db_int(0, azQuery[i], rid);
  }
  if( srcId && content_get(srcId, &src) ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    blob_delta_create(&src, pContent, &delta);
    size = blob_size(&delta);
    if( size>=blob_size(pContent)-50 ){
      size = 0;
    }else{
      blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
      blob_append(pXfer->pOut, blob_buffer(&delta), size);
      blob_appendf(pXfer->pOut, "\n", 1);
    }
    blob_reset(&delta);
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}

/*
** Send the file identified by rid.



*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int srcId){
  Blob content, uuid;
  int size = 0;


  if( db_exists("SELECT 1 FROM sent WHERE rid=%d", rid) ){
     return;
  }
  blob_zero(&uuid);
  if( pUuid==0 ){
    db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
    if( blob_size(&uuid)==0 ){
      return;
    }
    pUuid = &uuid;
  }
  if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
    blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
    pXfer->nIGot++;
    blob_reset(&uuid);
    return;
  }
  content_get(rid, &content);

  if( blob_size(&content)>100 ){










    size = send_as_delta(pXfer, rid, &content, pUuid, srcId);

  }



  if( size==0 ){
    int size = blob_size(&content);

    blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
    blob_append(pXfer->pOut, blob_buffer(&content), size);
    pXfer->nFile++;
  }else{
    pXfer->nDelta++;
  }
  db_multi_exec("INSERT INTO sent VALUES(%d)", rid);
  blob_reset(&uuid);

}








/*

** This routine runs when either client or server is notified that
** the other side things rid is a leaf manifest.  If we hold
** children of rid, then send them over to the other side.
*/

static void leaf_response(Xfer *pXfer, int rid){

  Stmt q1, q2;



  db_prepare(&q1,
      "SELECT cid, uuid FROM plink, blob"
      " WHERE blob.rid=plink.cid"
      "   AND plink.pid=%d",
      rid
  );
  while( db_step(&q1)==SQLITE_ROW ){
    Blob uuid;
    int cid;

    cid = db_column_int(&q1, 0);
    db_ephemeral_blob(&q1, 1, &uuid);
    send_file(pXfer, cid, &uuid, rid);
    db_prepare(&q2,
       "SELECT pid, uuid, fid FROM mlink, blob"

       " WHERE rid=fid AND mid=%d",
       cid
    );
    while( db_step(&q2)==SQLITE_ROW ){
      int pid, fid;
      pid = db_column_int(&q2, 0);

      db_ephemeral_blob(&q2, 1, &uuid);
      fid = db_column_int(&q2, 2);
      send_file(pXfer, fid, &uuid, pid);
    }
    db_finalize(&q2);
    if( blob_size(pXfer->pOut)<pXfer->mxSend ){
      leaf_response(pXfer, cid);


    }



  }




}






/*
** Sent a leaf message for every leaf.
*/
static void send_leaves(Xfer *pXfer){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM blob WHERE rid IN"

    "  (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);

    blob_appendf(pXfer->pOut, "leaf %s\n", zUuid);
  }
  db_finalize(&q);

}

/*
** Sen a gimme message for every phantom.
*/
static void request_phantoms(Xfer *pXfer){
  Stmt q;

  db_prepare(&q, "SELECT uuid FROM phantom JOIN blob USING(rid)");
  while( db_step(&q)==SQLITE_ROW ){

    const char *zUuid = db_column_text(&q, 0);


    blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
  }


  db_finalize(&q);
}


/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
**
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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446











447
448
449
450
451
452
453
454
455
456
457


458
459
460
461
462
463
464
465
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
495
496

497
498
499
500
501
502
503
504
505
506
507
508
509
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
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
** Process this message and form an appropriate reply.
*/
void page_xfer(void){
  int nToken;
  int isPull = 0;
  int isPush = 0;
  int nErr = 0;
  Blob line, errmsg, aToken[5];

  db_begin_transaction();
  blobarray_zero(aToken, count(aToken));
  cgi_set_content_type(g.zContentType);
  blob_zero(&errmsg);




  db_multi_exec(
     "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" /* Client has */
     "CREATE TEMP TABLE pending(rid INTEGER PRIMARY KEY);"  /* Client needs */
  );
  while( blob_line(&g.cgiIn, &line) ){
    nToken = blob_tokenize(&line, aToken, count(aToken));

    /*   file UUID SIZE \n CONTENT
    **   file UUID DELTASRC SIZE \n CONTENT
    **
    ** Accept a file from the client.
    */
    if( blob_eq(&aToken[0], "file") && nToken>=3 && nToken<=4 ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite
        nErr++;
        break;
      }
      xfer_accept_file(&g.cgiIn, aToken, nToken, &errmsg);
      if( blob_size(&errmsg) ){
        cgi_reset_content();
        @ error %T(blob_str(&errmsg))
        nErr++;
        break;
      }
    }else

    /*   gimme UUID
    **
    ** Client is requesting a file
    */
    if( blob_eq(&aToken[0], "gimme") && nToken==2 && blob_is_uuid(&aToken[1]) ){



      if( isPull ){
        db_multi_exec(
          "INSERT OR IGNORE INTO pending(rid) "
          "SELECT rid FROM blob WHERE uuid=%B AND size>=0", &aToken[1]
        );


      }
    }else

    /*   igot UUID
    **   leaf UUID
    **
    ** Client announces that it has a particular file
    */
    if( nToken==2
          && (blob_eq(&aToken[0], "igot") || blob_eq(&aToken[0],"leaf"))
          && blob_is_uuid(&aToken[1]) ){
      if( isPull || isPush ){
        int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &aToken[1]);
        if( rid>0 ){
          db_multi_exec(
            "INSERT OR IGNORE INTO onremote(rid) VALUES(%d)", rid
          );
          if( isPull && blob_eq(&aToken[0], "leaf") ){
            db_multi_exec(
              "INSERT OR IGNORE INTO pending(rid) "
              "SELECT cid FROM plink WHERE pid=%d", rid
            );
          }
        }else if( isPush ){
          content_put(0, blob_str(&aToken[1]));
        }











      }
    }else

    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive
    */
    if( nToken==3
               && (blob_eq(&aToken[0], "pull") || blob_eq(&aToken[0], "push"))
               && blob_is_uuid(&aToken[1]) && blob_is_uuid(&aToken[2]) ){


      const char *zSCode;
      const char *zPCode;

      zSCode = db_get("server-code", 0);
      if( zSCode==0 ){
        fossil_panic("missing server code");
      }
      if( blob_eq_str(&aToken[1], zSCode, -1) ){
        cgi_reset_content();
        @ error server\sloop
        nErr++;
        break;
      }
      zPCode = db_get("project-code", 0);
      if( zPCode==0 ){
        fossil_panic("missing project code");
      }
      if( !blob_eq_str(&aToken[2], zPCode, -1) ){
        cgi_reset_content();
        @ error wrong\sproject
        nErr++;
        break;
      }
      login_check_credentials();
      if( blob_eq(&aToken[0], "pull") ){
        if( !g.okRead ){
          cgi_reset_content();
          @ error not\sauthorized\sto\sread
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.okWrite ){
          cgi_reset_content();
          @ error not\sauthorized\sto\swrite
          nErr++;
          break;
        }

        isPush = 1;

      }
    }else

    /*    clone
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&aToken[0], "clone") ){
      login_check_credentials();
      if( !g.okRead || !g.okHistory ){
        cgi_reset_content();
        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      isPull = 1;
      @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
      db_multi_exec(
        "INSERT OR IGNORE INTO pending(rid) "
        "SELECT mid FROM mlink JOIN blob ON mid=rid"
      );
    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** Check for a valid login.  This has to happen before anything else.
    */
    if( blob_eq(&aToken[0], "login") && nToken==4 ){


      if( disableLogin ){
        g.okRead = g.okWrite = 1;
      }else{
        check_login(&aToken[1], &aToken[2], &aToken[3]);
      }
    }else

    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&line))
    }
    blobarray_reset(aToken, nToken);
  }

  /* The input message has now been processed.  Generate a reply. */
  if( isPush ){
    Stmt q;
    int nReq = 0;
    db_prepare(&q, "SELECT uuid, rid FROM phantom JOIN blob USING (rid)");
    while( db_step(&q)==SQLITE_ROW && nReq++ < 200 ){
      const char *zUuid = db_column_text(&q, 0);
      int rid = db_column_int(&q, 1);
      int xid = similar_record(rid, 0);
      @ gimme %s(zUuid)
      if( xid ){
        char *zXUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", xid);
        @ igot %s(zXUuid);
        free(zXUuid);
      }
    }
    db_finalize(&q);
  }
  if( isPull ){
    send_all_pending(0);
  }
  if( isPush || isPull ){
    /* Always send our leaves */
    Stmt q;
    db_prepare(&q, 
       "SELECT uuid FROM blob WHERE rid IN"
       "  (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zUuid = db_column_text(&q, 0);
      @ leaf %s(zUuid)
    }
    db_finalize(&q);
  }

  db_end_transaction(0);
}

/*
** COMMAND: test-xfer
**
** This command is used for debugging the server.  There is a single







|

|
|

|
>
>
>
>

|
<

|
|






|






|
|

|









|
>
>
>

<
<
|
|
>
>




<



|
|
|
<
<
<
<
<
|
|
<
<
<
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>









|
|
>
>







|









|






|














>

<







|









<
<
<
|






|
>
>



|







|

|

<
<

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







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
426
427
428
429

430
431
432
433
434
435





436
437



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
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
495
496
497
498
499
500
501
502
503
504
505
506
507

508
509
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




















553
554
555
556
557
558
559
** Process this message and form an appropriate reply.
*/
void page_xfer(void){
  int nToken;
  int isPull = 0;
  int isPush = 0;
  int nErr = 0;
  Xfer xfer;

  memset(&xfer, 0, sizeof(xfer));
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  cgi_set_content_type(g.zContentType);
  blob_zero(&xfer.err);
  xfer.pIn = &g.cgiIn;
  xfer.pOut = cgi_output_blob();

  db_begin_transaction();
  db_multi_exec(
     "CREATE TEMP TABLE sent(rid INTEGER PRIMARY KEY);"

  );
  while( blob_line(xfer.pIn, &xfer.line) ){
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));

    /*   file UUID SIZE \n CONTENT
    **   file UUID DELTASRC SIZE \n CONTENT
    **
    ** Accept a file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "file") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite
        nErr++;
        break;
      }
      xfer_accept_file(&xfer);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))
        nErr++;
        break;
      }
    }else

    /*   gimme UUID
    **
    ** Client is requesting a file
    */
    if( blob_eq(&xfer.aToken[0], "gimme")
     && xfer.nToken==2
     && blob_is_uuid(&xfer.aToken[1])
    ){
      if( isPull ){


        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        if( rid ){
          send_file(&xfer, rid, &xfer.aToken[1], 0);
        }
      }
    }else

    /*   igot UUID

    **
    ** Client announces that it has a particular file
    */
    if( xfer.nToken==2
     && blob_eq(&xfer.aToken[0], "igot")
     && blob_is_uuid(&xfer.aToken[1])





    ){
      if( isPush ){



        rid_from_uuid(&xfer.aToken[1], 1);
      }
    }else
  
    
    /*   leaf UUID
    **
    ** Client announces that it has a particular manifest
    */
    if( xfer.nToken==2
     && blob_eq(&xfer.aToken[0], "leaf")
     && blob_is_uuid(&xfer.aToken[1])
    ){
      if( isPull ){
        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        leaf_response(&xfer, rid);
      }
    }else

    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive
    */
    if( nToken==3
     && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
     && blob_is_uuid(&xfer.aToken[1])
     && blob_is_uuid(&xfer.aToken[2])
    ){
      const char *zSCode;
      const char *zPCode;

      zSCode = db_get("server-code", 0);
      if( zSCode==0 ){
        fossil_panic("missing server code");
      }
      if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
        cgi_reset_content();
        @ error server\sloop
        nErr++;
        break;
      }
      zPCode = db_get("project-code", 0);
      if( zPCode==0 ){
        fossil_panic("missing project code");
      }
      if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
        cgi_reset_content();
        @ error wrong\sproject
        nErr++;
        break;
      }
      login_check_credentials();
      if( blob_eq(&xfer.aToken[0], "pull") ){
        if( !g.okRead ){
          cgi_reset_content();
          @ error not\sauthorized\sto\sread
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.okWrite ){
          cgi_reset_content();
          @ error not\sauthorized\sto\swrite
          nErr++;
          break;
        }
        send_leaves(&xfer);
        isPush = 1;

      }
    }else

    /*    clone
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      login_check_credentials();
      if( !g.okRead || !g.okHistory ){
        cgi_reset_content();
        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      isPull = 1;
      @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))



      send_leaves(&xfer);
    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** Check for a valid login.  This has to happen before anything else.
    */
    if( blob_eq(&xfer.aToken[0], "login")
     && nToken==4
    ){
      if( disableLogin ){
        g.okRead = g.okWrite = 1;
      }else{
        check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3]);
      }
    }else

    /* Unknown message
    */
    {
      cgi_reset_content();
      @ error bad\scommand:\s%F(blob_str(&xfer.line))
    }
    blobarray_reset(xfer.aToken, xfer.nToken);
  }


  if( isPush ){


    request_phantoms(&xfer);









  }




















  db_end_transaction(0);
}

/*
** COMMAND: test-xfer
**
** This command is used for debugging the server.  There is a single
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637


638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
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
691
692
693
694
695
696
697
698
699
700
701
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
770
771
772
773
774
775
776
777
778

779
780
781
782
783








784
785
786

787
788

789
790
791
792
793
794


795
796

797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829

830
831
832
833
834
835
836
837
838
839
840



841
842
843
844
845
846
847
848
**
** Records are pushed to the server if pushFlag is true.  Records
** are pulled if pullFlag is true.  A full sync occurs if both are
** true.
*/
void client_sync(int pushFlag, int pullFlag, int cloneFlag){
  int go = 1;        /* Loop until zero */
  int nToken;
  const char *zSCode = db_get("server-code", "x");
  const char *zPCode = db_get("project-code", 0);
  int nFile = 0;
  int nMsg = 0;
  int nReq = 0;
  int nFileSend;
  int nNoFileCycle = 0;
  Blob send;        /* Text we are sending to the server */
  Blob recv;        /* Reply we got back from the server */
  Blob line;        /* A single line of the reply */
  Blob aToken[5];   /* A tokenization of line */
  Blob errmsg;      /* Error message */



  assert( pushFlag || pullFlag || cloneFlag );
  assert( !g.urlIsFile );          /* This only works for networking */

  db_begin_transaction();
  db_multi_exec(
    /* Records which we know the other side also has */
    "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
    /* Records we know the other side needs */
    "CREATE TEMP TABLE pending(rid INTEGER PRIMARY KEY);"
  );
  blobarray_zero(aToken, count(aToken));
  blob_zero(&send);
  blob_zero(&recv);
  blob_zero(&errmsg);


  while( go ){
    go = 0;
    nFile = nReq = nMsg = 0;

    /* Generate a request to be sent to the server.
    ** Always begin with a clone, pull, or push message
    */
    
    if( cloneFlag ){
      blob_appendf(&send, "clone\n");
      pushFlag = 0;
      pullFlag = 0;
      nMsg++;
    }else if( pullFlag ){
      blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);


      nMsg++;
    }
    if( pushFlag ){
      blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
      nMsg++;
    }

    if( pullFlag ){
      /* Send gimme message for every phantom that we hold.
      */
      Stmt q;
      db_prepare(&q, "SELECT uuid, rid FROM phantom JOIN blob USING (rid)");
      while( db_step(&q)==SQLITE_ROW && nReq<200 ){
        const char *zUuid = db_column_text(&q, 0);
        int rid = db_column_int(&q, 1);
        int xid = similar_record(rid, 0);
        blob_appendf(&send,"gimme %s\n", zUuid);
        nReq++;
        if( xid ){
          blob_appendf(&send, "igot %z\n",
             db_text(0, "SELECT uuid FROM blob WHERE rid=%d", xid));
        }
      }
      db_finalize(&q);
    }

    if( pushFlag ){
      /* Send the server any files that the server has requested */
      nFile += send_all_pending(&send);
    }

    if( pullFlag || pushFlag ){
      /* Always send our leaves */
      Stmt q;
      db_prepare(&q, 
         "SELECT uuid FROM blob WHERE rid IN"
         "  (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
      );
      while( db_step(&q)==SQLITE_ROW ){
        const char *zUuid = db_column_text(&q, 0);
        blob_appendf(&send, "leaf %s\n", zUuid);
        nMsg++;
      }
      db_finalize(&q);
    }

    /* Exchange messages with the server */

    printf("Send:      %3d files, %3d requests, %3d other msgs, %8d bytes\n",
            nFile, nReq, nMsg, blob_size(&send));
    nFileSend = nFile;


    nFile = nReq = nMsg = 0;
    http_exchange(&send, &recv);
    blob_reset(&send);

    /* Process the reply that came back from the server */
    while( blob_line(&recv, &line) ){
      nToken = blob_tokenize(&line, aToken, count(aToken));

      /*   file UUID SIZE \n CONTENT
      **   file UUID DELTASRC SIZE \n CONTENT
      **
      ** Receive a file transmitted from the other side
      */
      if( blob_eq(&aToken[0],"file") ){
        xfer_accept_file(&recv, aToken, nToken, &errmsg);
        nFile++;
        go = 1;
      }else

      /*   gimme UUID
      **
      ** Server is requesting a file
      */
      if( blob_eq(&aToken[0], "gimme") && nToken==2

               && blob_is_uuid(&aToken[1]) ){
        nReq++;

        if( pushFlag ){
          db_multi_exec(
            "INSERT OR IGNORE INTO pending(rid) "
            "SELECT rid FROM blob WHERE uuid=%B AND size>=0", &aToken[1]
          );
          go = 1;
        }
      }else
  
      /*   igot UUID
      **   leaf UUID
      **
      ** Server proclaims that it has a particular file.  A leaf message
      ** means that the file is a leaf manifest on the server.
      */
      if( nToken==2
              && (blob_eq(&aToken[0], "igot") || blob_eq(&aToken[0], "leaf"))
              && blob_is_uuid(&aToken[1]) ){
        int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &aToken[1]);
        nMsg++;
        if( rid>0 ){
          db_multi_exec(
            "INSERT OR IGNORE INTO onremote(rid) VALUES(%d)", rid
          );
          /* Add to the pending set all children of the server's leaves */
          if( pushFlag && blob_eq(&aToken[0], "leaf") ){
            db_multi_exec(
              "INSERT OR IGNORE INTO pending(rid) "
              "SELECT cid FROM plink WHERE pid=%d", rid
            );
            if( db_changes()>0 ){
              go = 1;
            }

          }
          if( pullFlag && !go && 
              db_exists("SELECT 1 FROM phantom WHERE rid=%d", rid) ){
            go = 1;
          }








        }else if( pullFlag ){
          go = 1;
          content_put(0, blob_str(&aToken[1]));

        }
      }else

  
      /*   push  SERVERCODE  PRODUCTCODE
      **
      ** Should only happen in response to a clone.
      */
      if( blob_eq(&aToken[0],"push") && nToken==3 && cloneFlag


              && blob_is_uuid(&aToken[1]) && blob_is_uuid(&aToken[2]) ){


        if( blob_eq_str(&aToken[1], zSCode, -1) ){
          fossil_fatal("server loop");
        }
        nMsg++;
        if( zPCode==0 ){
          zPCode = mprintf("%b", &aToken[2]);
          db_set("project-code", zPCode);
        }
        cloneFlag = 0;
        pullFlag = 1;
      }else

      /*   error MESSAGE
      **
      ** Report an error
      */        
      if( blob_eq(&aToken[0],"error") && nToken==2 ){
        char *zMsg = blob_terminate(&aToken[1]);
        defossilize(zMsg);
        blob_appendf(&errmsg, "server says: %s", zMsg);
      }else

      /* Unknown message */
      {
        blob_appendf(&errmsg, "unknown command: %b", &aToken[0]);
      }

      if( blob_size(&errmsg) ){
        fossil_fatal("%b", &errmsg);
      }
      blobarray_reset(aToken, nToken);
    }
    printf("Received:  %3d files, %3d requests, %3d other msgs, %8d bytes\n",

            nFile, nReq, nMsg, blob_size(&recv));
    blob_reset(&recv);
    if( nFileSend + nFile==0 ){
      nNoFileCycle++;
      if( nNoFileCycle>1 ){
        go = 0;
      }
    }else{
      nNoFileCycle = 0;
    }
    nFile = nReq = nMsg = 0;



  };
  http_close();
  db_end_transaction(0);
  db_multi_exec(
    "DROP TABLE onremote;"
    "DROP TABLE pending;"
  );
}







<


<


|



|
|
|
>
>






<
<
<
|

|


|




|












>
>







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

>

|
|
>
>
|




|
|






|
|
<
<






|
>
|
<
>

<
|
|
<
<




<

|
<

|
|
|
<
<
<
<
<
|
<
<
<
<
<
<
|
|
|
>
|
<
<
<
|
>
>
>
>
>
>
>
>
|
<
|
>


>





|
>
>
|
|
>
|




|










|
|

|




|


|
|

|


>
|

|







|
>
>
>



<
<
<
<

593
594
595
596
597
598
599

600
601

602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618



619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650







































651
652
653
654
655
656
657
658
659
660
661
662
663
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
691

692
693
694
695





696






697
698
699
700
701



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
770
771
772
773
774
775
776
777
778




779
**
** Records are pushed to the server if pushFlag is true.  Records
** are pulled if pullFlag is true.  A full sync occurs if both are
** true.
*/
void client_sync(int pushFlag, int pullFlag, int cloneFlag){
  int go = 1;        /* Loop until zero */

  const char *zSCode = db_get("server-code", "x");
  const char *zPCode = db_get("project-code", 0);

  int nMsg = 0;
  int nReq = 0;
  int nFileSend = 0;
  int nNoFileCycle = 0;
  Blob send;        /* Text we are sending to the server */
  Blob recv;        /* Reply we got back from the server */
  Xfer xfer;        /* Transfer data */

  memset(&xfer, 0, sizeof(xfer));
  xfer.pIn = &recv;
  xfer.pOut = &send;

  assert( pushFlag || pullFlag || cloneFlag );
  assert( !g.urlIsFile );          /* This only works for networking */

  db_begin_transaction();
  db_multi_exec(



    "CREATE TEMP TABLE sent(rid INTEGER PRIMARY KEY);"
  );
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  blob_zero(&send);
  blob_zero(&recv);
  blob_zero(&xfer.err);


  while( go ){
    go = 0;
    nReq = nMsg = 0;

    /* Generate a request to be sent to the server.
    ** Always begin with a clone, pull, or push message
    */
    
    if( cloneFlag ){
      blob_appendf(&send, "clone\n");
      pushFlag = 0;
      pullFlag = 0;
      nMsg++;
    }else if( pullFlag ){
      blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
      send_leaves(&xfer);
      request_phantoms(&xfer);
      nMsg++;
    }
    if( pushFlag ){
      blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
      nMsg++;
    }








































    /* Exchange messages with the server */
    nFileSend = xfer.nFile + xfer.nDelta + xfer.nDanglingFile;
    printf("Send:      %3d files, %3d requests, %3d other msgs, %8d bytes\n",
            nFileSend, nReq, nMsg, blob_size(&send));
    xfer.nFile = 0;
    xfer.nDelta = 0;
    xfer.nDanglingFile = 0;
    nReq = nMsg = 0;
    http_exchange(&send, &recv);
    blob_reset(&send);

    /* Process the reply that came back from the server */
    while( blob_line(&recv, &xfer.line) ){
      xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));

      /*   file UUID SIZE \n CONTENT
      **   file UUID DELTASRC SIZE \n CONTENT
      **
      ** Receive a file transmitted from the other side
      */
      if( blob_eq(&xfer.aToken[0],"file") ){
        xfer_accept_file(&xfer);


      }else

      /*   gimme UUID
      **
      ** Server is requesting a file
      */
      if( blob_eq(&xfer.aToken[0], "gimme")
       && xfer.nToken==2
       && blob_is_uuid(&xfer.aToken[1])

      ){
        if( pushFlag ){

          int rid = rid_from_uuid(&xfer.aToken[1], 0);
          send_file(&xfer, rid, &xfer.aToken[1], 0);


        }
      }else
  
      /*   igot UUID

      **
      ** Server announces that it has a particular file

      */
      if( xfer.nToken==2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])





      ){






        if( pullFlag ){
          rid_from_uuid(&xfer.aToken[1], 1);
        }
      }else
    



      
      /*   leaf UUID
      **
      ** Server announces that it has a particular manifest
      */
      if( xfer.nToken==2
       && blob_eq(&xfer.aToken[0], "leaf")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        if( pushFlag ){

          int rid = rid_from_uuid(&xfer.aToken[1], 0);
          leaf_response(&xfer, rid);
        }
      }else
  
  
      /*   push  SERVERCODE  PRODUCTCODE
      **
      ** Should only happen in response to a clone.
      */
      if( blob_eq(&xfer.aToken[0],"push")
       && xfer.nToken==3
       && cloneFlag
       && blob_is_uuid(&xfer.aToken[1])
       && blob_is_uuid(&xfer.aToken[2])
      ){
        if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
          fossil_fatal("server loop");
        }
        nMsg++;
        if( zPCode==0 ){
          zPCode = mprintf("%b", &xfer.aToken[2]);
          db_set("project-code", zPCode);
        }
        cloneFlag = 0;
        pullFlag = 1;
      }else

      /*   error MESSAGE
      **
      ** Report an error
      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        blob_appendf(&xfer.err, "server says: %s", zMsg);
      }else

      /* Unknown message */
      {
        blob_appendf(&xfer.err, "unknown command: %b", &xfer.aToken[0]);
      }

      if( blob_size(&xfer.err) ){
        fossil_fatal("%b", &xfer.err);
      }
      blobarray_reset(xfer.aToken, xfer.nToken);
    }
    printf("Received:  %3d files, %3d requests, %3d other msgs, %8d bytes\n",
            xfer.nFile + xfer.nDelta + xfer.nDanglingFile,
            nReq, nMsg, blob_size(&recv));
    blob_reset(&recv);
    if( nFileSend + xfer.nFile + xfer.nDelta + xfer.nDanglingFile==0 ){
      nNoFileCycle++;
      if( nNoFileCycle>1 ){
        go = 0;
      }
    }else{
      nNoFileCycle = 0;
    }
    nReq = nMsg = 0;
    xfer.nFile = 0;
    xfer.nDelta = 0;
    xfer.nDanglingFile = 0;
  };
  http_close();
  db_end_transaction(0);




}