Fossil

Check-in [e1c1877c99]
Login

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

Overview
Comment:Sync using clusters appears to work. More testing is needed before we go live.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: e1c1877c99bf13c0ace4c6f0ba74c9e304aca372
User & Date: drh 2007-09-08 16:01:28.000
Context
2007-09-09
17:51
Cluster-based synchronization appears to be working. ... (check-in: 48c4e69d2b user: drh tags: trunk)
2007-09-08
16:01
Sync using clusters appears to work. More testing is needed before we go live. ... (check-in: e1c1877c99 user: drh tags: trunk)
2007-09-03
01:28
Add the unclustered table and the M lines to manifests. Process clusters in manifest_parse and manifest_crossref. The xfer process still does not yet use clusters though so it is still compatible. But that is about to change. ... (check-in: ba486fec5a user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to ideas.txt.
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
   mode: (readonly|appendonly|readwrite)
   attachment: UUID name description

   * Header ends with a blank line.  wiki content follows.

Cluster format:

   "Cluster"
   (UUID\n)+

   * Cluster generated in server mode only.
   * Embargo cluster that reference phantoms or other embargoed clusters.
   * Never send or ihave an embargoed cluster

New sync algorithm based on clusters:








|
|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
   mode: (readonly|appendonly|readwrite)
   attachment: UUID name description

   * Header ends with a blank line.  wiki content follows.

Cluster format:

       M+ uuid
       Z manifest-cksum

   * Cluster generated in server mode only.
   * Embargo cluster that reference phantoms or other embargoed clusters.
   * Never send or ihave an embargoed cluster

New sync algorithm based on clusters:

65
66
67
68
69
70
71

72
73
74
75
76
77
78
     * Client sends file message for all files in wanted.
     * Client sends ihave messages for each entry in unclustered
     ------
     * Server receives file message
     * Server creates phantoms for unknown ihaves
     * Server sends gimme messages for all phantoms
     ------

     * For each gimme message add an entry to wanted
     * Halt if the wanted table is empty

Details on new pull algorithm:

   Loop:
     * Client sends login and "pull" record







>







65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
     * Client sends file message for all files in wanted.
     * Client sends ihave messages for each entry in unclustered
     ------
     * Server receives file message
     * Server creates phantoms for unknown ihaves
     * Server sends gimme messages for all phantoms
     ------
     * Client clears its unsent table
     * For each gimme message add an entry to wanted
     * Halt if the wanted table is empty

Details on new pull algorithm:

   Loop:
     * Client sends login and "pull" record
Changes to src/checkin.c.
389
390
391
392
393
394
395

396
397
398
399
400
401
402
    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);

  /* Create the manifest */
  blob_zero(&manifest);
  if( blob_size(&comment)==0 ){
    blob_append(&comment, "(no comment)", -1);







>







389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    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_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
  }
  db_finalize(&q);

  /* Create the manifest */
  blob_zero(&manifest);
  if( blob_size(&comment)==0 ){
    blob_append(&comment, "(no comment)", -1);
449
450
451
452
453
454
455

456
457
458
459
460
461
462
  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);
  zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
  blob_zero(&muuid);
  blob_appendf(&muuid, "%s\n", zUuid);







>







450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
  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);
  }
  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
  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);
  zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
  blob_zero(&muuid);
  blob_appendf(&muuid, "%s\n", zUuid);
Changes to src/content.c.
232
233
234
235
236
237
238







239
240
241
242
243
244
245

  /* 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







>
>
>
>
>
>
>







232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

  /* 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);
  }
  
  /* Add the element to the unclustered table if it is not a
  ** a phantom
  */
  if( pBlob ){
    db_multi_exec("INSERT OR IGNORE INTO unclustered 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
Changes to src/manifest.c.
347
348
349
350
351
352
353
354
355

356
357
358
359
360
361

362
363
364
365
366
    db_multi_exec(
      "INSERT INTO event(type,mtime,objid,user,comment)"
      "VALUES('ci',%.17g,%d,%Q,%Q)",
      m.rDate, rid, m.zUser, m.zComment
    );
  }
  for(i=0; i<m.nCChild; i++){
    static Stmt dc;
    db_static_prepare(&dc,

      "DELETE FROM unclustered WHERE rid ="
      " (SELECT rid FROM blob WHERE uuid=:u)"
    );
    db_bind_text(&dc, ":u", m.azCChild[i]);
    db_step(&dc);
    db_reset(&dc);

  }
  db_end_transaction(0);
  manifest_clear(&m);
  return 1;
}







|
|
>
|
<
<
<
<
<
>





347
348
349
350
351
352
353
354
355
356
357





358
359
360
361
362
363
    db_multi_exec(
      "INSERT INTO event(type,mtime,objid,user,comment)"
      "VALUES('ci',%.17g,%d,%Q,%Q)",
      m.rDate, rid, m.zUser, m.zComment
    );
  }
  for(i=0; i<m.nCChild; i++){
    int rid;
    rid = uuid_to_rid(m.azCChild[i], 1);
    if( rid>0 ){
      db_multi_exec("DELETE FROM unclustered WHERE rid=%d", rid);





    }
  }
  db_end_transaction(0);
  manifest_clear(&m);
  return 1;
}
Changes to src/rebuild.c.
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    int size = db_column_int(&s, 1);
    if( size>=0 ){
      Blob content;
      content_get(rid, &content);
      manifest_crosslink(rid, &content);
      blob_reset(&content);
    }else{
      db_multi_exec("INSERT INTO phantom VALUES(%d)", rid);
    }
  }
  return errCnt;
}

/*
** COMMAND:  rebuild







|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    int size = db_column_int(&s, 1);
    if( size>=0 ){
      Blob content;
      content_get(rid, &content);
      manifest_crosslink(rid, &content);
      blob_reset(&content);
    }else{
      db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
    }
  }
  return errCnt;
}

/*
** COMMAND:  rebuild
Changes to src/xfer.c.
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
    pXfer->nDeltaSent++;
  }
  remote_has(rid);
  blob_reset(&uuid);
}

/*
** Send the file identified by mid and pUuid.  If that file happens
** to be a manifest, then also send all of the associated content
** files for that manifest.  If the file is not a manifest, then this
** routine is the equivalent of send_file().
*/
static void send_manifest(Xfer *pXfer, int mid, Blob *pUuid, int srcId){
  Stmt q2;
  send_file(pXfer, mid, pUuid, srcId);
  db_prepare(&q2,
     "SELECT pid, uuid, fid FROM mlink, blob"
     " WHERE rid=fid AND mid=%d",
     mid
  );
  while( db_step(&q2)==SQLITE_ROW ){
    int pid, fid;
    Blob uuid;
    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);
}

/*
** This routine runs when either client or server is notified that
** the other side thinks 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;
  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_manifest(pXfer, cid, &uuid, rid);
    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);
}

/*
** Sent leaf content for every leaf that is not found in the
** onremote table.  This is intended to send leaf content for
** every leaf that is unknown on the remote end.
**
** In addition, we might send "igot" messages for a few generations of
** parents of the unknown leaves.  This will speed the transmission
** of new branches.  
*/
static void send_unknown_leaf_content(Xfer *pXfer){
  Stmt q1;
  db_prepare(&q1,
    "SELECT rid, uuid FROM blob WHERE rid IN"
    "  (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
    "  AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"
  );
  while( db_step(&q1)==SQLITE_ROW ){
    Blob uuid;
    int cid;

    cid = db_column_int(&q1, 0);
    db_ephemeral_blob(&q1, 1, &uuid);
    send_manifest(pXfer, cid, &uuid, 0);
  }
  db_finalize(&q1);
}

/*
** 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);







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







232
233
234
235
236
237
238






























































































239
240
241
242
243
244
245
246
    pXfer->nDeltaSent++;
  }
  remote_has(rid);
  blob_reset(&uuid);
}

/*






























































































** Send 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);
394
395
396
397
398
399
400



























































401
402
403
404
405
406
407
      g.zLogin = mprintf("%b", pLogin);
      g.zNonce = mprintf("%b", pNonce);
    }
  }
  db_reset(&q);
}





























































/*
** If this variable is set, disable login checks.  Used for debugging
** only.
*/
static int disableLogin = 0;








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







300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
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
      g.zLogin = mprintf("%b", pLogin);
      g.zNonce = mprintf("%b", pNonce);
    }
  }
  db_reset(&q);
}

/*
** Send the content of all files in the unsent table.
**
** This is really just an optimization.  If you clear the
** unsent table, all the right files will still get transferred.
** It just might require an extra round trip or two.
*/
static void send_unsent(Xfer *pXfer){
  Stmt q;
  db_prepare(&q, "SELECT rid FROM unsent");
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    send_file(pXfer, rid, 0, 0);
  }
  db_finalize(&q);
  db_multi_exec("DELETE FROM unsent");
}

/*
** Check to see if the number of unclustered entries is greater than
** 100 and if it is, form a new cluster.
*/
static void create_cluster(void){
  Blob cluster, cksum;
  Stmt q;
  int rid;
  if( db_int(0, "SELECT count(*) FROM unclustered")<10 ){
    return;
  }
  blob_zero(&cluster);
  db_prepare(&q, "SELECT uuid FROM unclustered JOIN blob USING(rid)"
                 " ORDER BY 1");
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
  md5sum_blob(&cluster, &cksum);
  blob_appendf(&cluster, "Z %b\n", &cksum);
  blob_reset(&cksum);
  db_multi_exec("DELETE FROM unclustered");
  content_put(&cluster, 0, 0);
  blob_reset(&cluster);
}

/*
** Send an igot message for every entry in unclustered table.
** Return the number of messages sent.
*/
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  db_prepare(&q, "SELECT uuid FROM unclustered JOIN blob USING(rid)");
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  return cnt;
}

/*
** If this variable is set, disable login checks.  Used for debugging
** only.
*/
static int disableLogin = 0;

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
        nErr++;
        break;
      }
    }else

    /*   gimme UUID
    **
    ** Client is requesting a file.  If the file is a manifest,
    ** the server can assume that the client also needs all content
    ** files associated with that manifest.
    */
    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_manifest(&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
    ** the server has children of this leaf, then send those
    ** children back to the client.  If the server lacks this
    ** leaf, request it.
    */
    if( xfer.nToken==2
     && blob_eq(&xfer.aToken[0], "leaf")
     && blob_is_uuid(&xfer.aToken[1])
    ){
      int rid = rid_from_uuid(&xfer.aToken[1], 0);
      if( rid ){
        remote_has(rid);
        if( isPull ){
          leaf_response(&xfer, rid);
        }
      }else if( isPush ){
        content_put(0, blob_str(&xfer.aToken[1]), 0);
      }
    }else

    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive.  The server should
    ** verify that the project code matches and that the server code
    ** does not match.
    */







|
<
<








|


















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







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
        nErr++;
        break;
      }
    }else

    /*   gimme UUID
    **
    ** Client is requesting a file.  Send it.


    */
    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
  
    






















    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive.  The server should
    ** verify that the project code matches and that the server code
    ** does not match.
    */
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
585
586
587
588
589
590
591
592
593
594
595
596
      }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") ){
      int rootid;
      login_check_credentials();
      if( !g.okClone ){
        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"))
      rootid = db_int(0, 
          "SELECT pid FROM plink AS a"
          " WHERE NOT EXISTS(SELECT 1 FROM plink WHERE cid=a.pid)"
      );
      if( rootid ){
        send_file(&xfer, rootid, 0, -1);
        leaf_response(&xfer, rootid);
      }
    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** Check for a valid login.  This has to happen before anything else.
    ** The client can send multiple logins.  Permissions are cumulative.
    */







<









<









<
<
<
<
<
<
<
<







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
      }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(&xfer.aToken[0], "clone") ){

      login_check_credentials();
      if( !g.okClone ){
        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"))








    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** Check for a valid login.  This has to happen before anything else.
    ** The client can send multiple logins.  Permissions are cumulative.
    */
612
613
614
615
616
617
618

619
620
621
622
623
624
625
626
    }
    blobarray_reset(xfer.aToken, xfer.nToken);
  }
  if( isPush ){
    request_phantoms(&xfer);
  }
  if( isPull ){

    send_unknown_leaf_content(&xfer);
  }
  db_end_transaction(0);
}

/*
** COMMAND: test-xfer
**







>
|







543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
    }
    blobarray_reset(xfer.aToken, xfer.nToken);
  }
  if( isPush ){
    request_phantoms(&xfer);
  }
  if( isPull ){
    create_cluster();
    send_unclustered(&xfer);
  }
  db_end_transaction(0);
}

/*
** COMMAND: test-xfer
**
714
715
716
717
718
719
720


721

722
723
724
725
726
727
728
    int newPhantom = 0;

    /* Generate gimme messages for phantoms and leaf messages
    ** for all leaves.
    */
    if( pullFlag ){
      request_phantoms(&xfer);


      send_leaves(&xfer);

    }

    /* Exchange messages with the server */
    nFileSend = xfer.nFileSent + xfer.nDeltaSent;
    printf("Send:      %10d bytes, %3d messages, %3d files (%d+%d)\n",
            blob_size(&send), nMsg+xfer.nGimmeSent+xfer.nIGotSent,
            nFileSend, xfer.nFileSent, xfer.nDeltaSent);







>
>
|
>







646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
    int newPhantom = 0;

    /* Generate gimme messages for phantoms and leaf messages
    ** for all leaves.
    */
    if( pullFlag ){
      request_phantoms(&xfer);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nMsg += send_unclustered(&xfer);
    }

    /* Exchange messages with the server */
    nFileSend = xfer.nFileSent + xfer.nDeltaSent;
    printf("Send:      %10d bytes, %3d messages, %3d files (%d+%d)\n",
            blob_size(&send), nMsg+xfer.nGimmeSent+xfer.nIGotSent,
            nFileSend, xfer.nFileSent, xfer.nDeltaSent);
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
      if( blob_eq(&xfer.aToken[0], "gimme")
       && xfer.nToken==2
       && blob_is_uuid(&xfer.aToken[1])
      ){
        nMsg++;
        if( pushFlag ){
          int rid = rid_from_uuid(&xfer.aToken[1], 0);
          send_manifest(&xfer, rid, &xfer.aToken[1], 0);
        }
      }else
  
      /*   igot UUID
      **
      ** Server announces that it has a particular file.  If this is
      ** not a file that we have and we are pulling, then create a







|







706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
      if( blob_eq(&xfer.aToken[0], "gimme")
       && xfer.nToken==2
       && blob_is_uuid(&xfer.aToken[1])
      ){
        nMsg++;
        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 this is
      ** not a file that we have and we are pulling, then create a
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
        if( rid==0 ){
          rid = rid_from_uuid(&xfer.aToken[1], 0);
        }
        remote_has(rid);
      }else
    
      
      /*   leaf UUID
      **
      ** Server announces that it has a particular manifest.  Send
      ** any children of this leaf that we have if we are pushing.
      ** Make the leaf a phantom if we are pulling.  Remember that the
      ** remote end has the specified UUID.
      */
      if( xfer.nToken==2
       && blob_eq(&xfer.aToken[0], "leaf")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        nMsg++;
        if( pushFlag && rid ){
          leaf_response(&xfer, rid);
        }
        if( pullFlag && rid==0 ){
          rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
          newPhantom = 1;
        }
        remote_has(rid);
      }else
  
  
      /*   push  SERVERCODE  PRODUCTCODE
      **
      ** Should only happen in response to a clone.  This message tells
      ** the client what product to use for the new database.
      */
      if( blob_eq(&xfer.aToken[0],"push")
       && xfer.nToken==3







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







738
739
740
741
742
743
744
























745
746
747
748
749
750
751
        if( rid==0 ){
          rid = rid_from_uuid(&xfer.aToken[1], 0);
        }
        remote_has(rid);
      }else
    
      
























      /*   push  SERVERCODE  PRODUCTCODE
      **
      ** Should only happen in response to a clone.  This message tells
      ** the client what product to use for the new database.
      */
      if( blob_eq(&xfer.aToken[0],"push")
       && xfer.nToken==3