Fossil

Check-in [a3dcfe75]
Login

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

Overview
Comment:More work on unversioned file sync.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | unversioned-files
Files: files | file ages | folders
SHA1:a3dcfe759541d1f266643241d2300aca7c358c1e
User & Date: drh 2016-08-09 15:29:27
Context
2016-08-09
17:18
Fixes to the unversioned file sync protocol. check-in: 5d913409 user: drh tags: unversioned-files
15:29
More work on unversioned file sync. check-in: a3dcfe75 user: drh tags: unversioned-files
12:37
Change the schema for the unversioned table. Add some initial code to do unversioned sync, but the code is incomplete and untested. check-in: 73932a32 user: drh tags: unversioned-files
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/sync.c.

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
...
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
...
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
...
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

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
static void process_sync_args(unsigned *pConfigFlags, unsigned *pSyncFlags){




  const char *zUrl = 0;
  const char *zHttpAuth = 0;
  unsigned configSync = 0;
  unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW;
  int urlOptional = 0;
  if( find_option("autourl",0,0)!=0 ){
    urlOptional = 1;
    urlFlags = 0;
  }
  zHttpAuth = find_option("httpauth","B",1);
  if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER;

  if( find_option("private",0,0)!=0 ){
    *pSyncFlags |= SYNC_PRIVATE;
  }
  if( find_option("verbose","v",0)!=0 ){
    *pSyncFlags |= SYNC_VERBOSE;
  }
  /* The --verily option to sync, push, and pull forces extra igot cards
  ** to be exchanged.  This can overcome malfunctions in the sync protocol.
  */
  if( find_option("verily",0,0)!=0 ){
    *pSyncFlags |= SYNC_RESYNC;

  }
  if( find_option("uv",0,0)!=0 ){
    *pSyncFlags |= SYNC_UNVERSIONED;
  }



  url_proxy_options();
  clone_ssh_find_options();
  db_find_and_open_repository(0, 0);
  db_open_config(0, 0);
  if( g.argc==2 ){
    if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN;
  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( urlFlags & URL_REMEMBER ){
................................................................................
**                              to ensure no content is overlooked
**
** See also: clone, config pull, push, remote-url, sync
*/
void pull_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PULL;
  process_sync_args(&configFlags, &syncFlags);

  /* We should be done with options.. */
  verify_all_options();

  client_sync(syncFlags, configFlags, 0);
}

................................................................................
**                              to ensure no content is overlooked
**
** See also: clone, config push, pull, remote-url, sync
*/
void push_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PUSH;
  process_sync_args(&configFlags, &syncFlags);

  /* We should be done with options.. */
  verify_all_options();

  if( db_get_boolean("dont-push",0) ){
    fossil_fatal("pushing is prohibited: the 'dont-push' option is set");
  }
................................................................................
**                              to ensure no content is overlooked
**
** See also: clone, pull, push, remote-url
*/
void sync_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PUSH|SYNC_PULL;
  process_sync_args(&configFlags, &syncFlags);

  /* We should be done with options.. */
  verify_all_options();

  if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH;
  client_sync(syncFlags, configFlags, 0);
  if( (syncFlags & SYNC_PUSH)==0 ){
    fossil_warning("pull only: the 'dont-push' option is set");
  }
}












/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off?
**
** Query and/or change the default server URL used by the "pull", "push",







|
>
>
>
>











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




>
>
>


|







 







|







 







|







 







|










>
>
>
>
>
>
>
>
>
>
>







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
...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
...
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

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
static void process_sync_args(
  unsigned *pConfigFlags,      /* Write configuration flags here */
  unsigned *pSyncFlags,        /* Write sync flags here */
  int uvOnly                   /* Special handling flags for UV sync */
){
  const char *zUrl = 0;
  const char *zHttpAuth = 0;
  unsigned configSync = 0;
  unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW;
  int urlOptional = 0;
  if( find_option("autourl",0,0)!=0 ){
    urlOptional = 1;
    urlFlags = 0;
  }
  zHttpAuth = find_option("httpauth","B",1);
  if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER;
  if( !uvOnly ){
    if( find_option("private",0,0)!=0 ){
      *pSyncFlags |= SYNC_PRIVATE;
    }



    /* The --verily option to sync, push, and pull forces extra igot cards
    ** to be exchanged.  This can overcome malfunctions in the sync protocol.
    */
    if( find_option("verily",0,0)!=0 ){
      *pSyncFlags |= SYNC_RESYNC;
    }
  }
  if( find_option("uv",0,0)!=0 ){
    *pSyncFlags |= SYNC_UNVERSIONED;
  }
  if( find_option("verbose","v",0)!=0 ){
    *pSyncFlags |= SYNC_VERBOSE;
  }
  url_proxy_options();
  clone_ssh_find_options();
  if( !uvOnly ) db_find_and_open_repository(0, 0);
  db_open_config(0, 0);
  if( g.argc==2 ){
    if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN;
  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( urlFlags & URL_REMEMBER ){
................................................................................
**                              to ensure no content is overlooked
**
** See also: clone, config pull, push, remote-url, sync
*/
void pull_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PULL;
  process_sync_args(&configFlags, &syncFlags, 0);

  /* We should be done with options.. */
  verify_all_options();

  client_sync(syncFlags, configFlags, 0);
}

................................................................................
**                              to ensure no content is overlooked
**
** See also: clone, config push, pull, remote-url, sync
*/
void push_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PUSH;
  process_sync_args(&configFlags, &syncFlags, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( db_get_boolean("dont-push",0) ){
    fossil_fatal("pushing is prohibited: the 'dont-push' option is set");
  }
................................................................................
**                              to ensure no content is overlooked
**
** See also: clone, pull, push, remote-url
*/
void sync_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PUSH|SYNC_PULL;
  process_sync_args(&configFlags, &syncFlags, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH;
  client_sync(syncFlags, configFlags, 0);
  if( (syncFlags & SYNC_PUSH)==0 ){
    fossil_warning("pull only: the 'dont-push' option is set");
  }
}

/*
** Handle the "fossil unversioned sync" command.
*/
void sync_unversioned(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_UNVERSIONED;
  process_sync_args(&configFlags, &syncFlags, 1);
  verify_all_options();
  client_sync(syncFlags, 0, 0);
}

/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off?
**
** Query and/or change the default server URL used by the "pull", "push",

Changes to src/unversioned.c.

99
100
101
102
103
104
105


































106
107
108
109
110
111
112
...
136
137
138
139
140
141
142


143
144
145
146
147
148
149
...
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
      blob_uncompress(pContent, pContent);
    }
    rc = 0;
  }
  db_finalize(&q);
  return rc;
}



































/*
** COMMAND: unversioned
**
** Usage: %fossil unversioned SUBCOMMAND ARGS...
**
** Unversioned files (UV-files) are artifacts that are synced and are available
................................................................................
**                         Changes are not pushed to other repositories until
**                         the next sync.
**
**    sync ?URL?           Synchronize the state of all unversioned files with
**                         the remote repository URL.  The most recent version of
**                         each file is propagate to all repositories and all
**                         prior versions are permanently forgotten.


**
** Options:
**
**   --mtime TIMESTAMP     Use TIMESTAMP instead of "now" for "add" and "rm".
*/
void unversioned_cmd(void){
  const char *zCmd;
................................................................................
           db_column_int(&q,2),
           db_column_text(&q,3),
           zNoContent
        );
      }
    }
    db_finalize(&q);
  }else if( memcmp(zCmd, "revert", nCmd)==0 || memcmp(zCmd,"sync",nCmd)==0 ){
    fossil_fatal("not yet implemented...");
  }else if( memcmp(zCmd, "rm", nCmd)==0 ){
    int i;
    verify_all_options();
    db_begin_transaction();
    for(i=3; i<g.argc; i++){
      db_multi_exec(
................................................................................
        "UPDATE unversioned"
        "   SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
        mtime, g.argv[i]
      );
    }
    db_unset("uv-hash", 0);
    db_end_transaction(0);
















  }else{
    usage("add|cat|export|ls|revert|rm|sync");
  }
}







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







 







>
>







 







|







 







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

|


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
...
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
...
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
      blob_uncompress(pContent, pContent);
    }
    rc = 0;
  }
  db_finalize(&q);
  return rc;
}

/*
** Check the status of unversioned file zName.  Return an integer status
** code as follows:
**
**    0:     zName does not exist in the unversioned table.
**    1:     zName exists and should be replaced by mtime/zHash.
**    2:     zName exists and is the same as zHash but has a older mtime
**    3:     zName exists and is identical to mtime/zHash in all respects.
**    4:     zName exists and is the same as zHash but has a newer mtime.
**    5:     zName exists and should override mtime/zHash.
*/
int unversioned_status(const char *zName, sqlite3_int64 mtime, const char *zHash){
  int iStatus = 0;
  Stmt q;
  db_prepare(&q, "SELECT mtime, hash FROM unversioned WHERE name=%Q", zName);
  if( db_step(&q)==SQLITE_ROW ){
    const char *zLocalHash = db_column_text(&q, 1);
    int hashCmp;
    sqlite3_int64 iLocalMtime = db_column_int64(&q, 0);
    int mtimeCmp = iLocalMtime<mtime ? -1 : (iLocalMtime==mtime ? 0 : +1);
    if( zLocalHash==0 ) zLocalHash = "-";
    hashCmp = strcmp(zLocalHash, zHash);
    if( hashCmp==0 ){
      iStatus = 3 + mtimeCmp;
    }else if( mtimeCmp<0 || (mtimeCmp==0 && hashCmp<0) ){
      iStatus = 1;
    }else{
      iStatus = 5;
    }
  }
  db_finalize(&q);
  return iStatus;
}

/*
** COMMAND: unversioned
**
** Usage: %fossil unversioned SUBCOMMAND ARGS...
**
** Unversioned files (UV-files) are artifacts that are synced and are available
................................................................................
**                         Changes are not pushed to other repositories until
**                         the next sync.
**
**    sync ?URL?           Synchronize the state of all unversioned files with
**                         the remote repository URL.  The most recent version of
**                         each file is propagate to all repositories and all
**                         prior versions are permanently forgotten.
**
**    touch FILE ...       Update the TIMESTAMP on all of the listed files
**
** Options:
**
**   --mtime TIMESTAMP     Use TIMESTAMP instead of "now" for "add" and "rm".
*/
void unversioned_cmd(void){
  const char *zCmd;
................................................................................
           db_column_int(&q,2),
           db_column_text(&q,3),
           zNoContent
        );
      }
    }
    db_finalize(&q);
  }else if( memcmp(zCmd, "revert", nCmd)==0 ){
    fossil_fatal("not yet implemented...");
  }else if( memcmp(zCmd, "rm", nCmd)==0 ){
    int i;
    verify_all_options();
    db_begin_transaction();
    for(i=3; i<g.argc; i++){
      db_multi_exec(
................................................................................
        "UPDATE unversioned"
        "   SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
        mtime, g.argv[i]
      );
    }
    db_unset("uv-hash", 0);
    db_end_transaction(0);
  }else if( memcmp(zCmd,"sync",nCmd)==0 ){
    g.argv[1] = "sync";
    g.argv[2] = "--uv";
    sync_unversioned();
  }else if( memcmp(zCmd, "touch", nCmd)==0 ){
    int i;
    verify_all_options();
    db_begin_transaction();
    for(i=3; i<g.argc; i++){
      db_multi_exec(
        "UPDATE unversioned SET mtime=%lld WHERE name=%Q",
        mtime, g.argv[i]
      );
    }
    db_unset("uv-hash", 0);
    db_end_transaction(0);
  }else{
    usage("add|cat|export|ls|revert|rm|sync|touch");
  }
}

Changes to src/xfer.c.

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
...
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
...
387
388
389
390
391
392
393

394
395
396
397
398
399
400
...
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
....
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
....
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
....
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
....
1703
1704
1705
1706
1707
1708
1709

1710






1711
1712
1713
1714
1715
1716
1717
....
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842

1843
1844
1845
1846
1847
1848
1849
....
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017



2018
2019
2020
2021
2022
2023
2024
2025



2026























2027
2028
2029
2030
2031
2032
2033
....
2134
2135
2136
2137
2138
2139
2140

2141
2142
2143
2144
2145
2146
2147
**
** 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 "0", 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 size
** is too big to transmit and so the "\n CONTENT" is omitted.


*/
static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){
  sqlite3_int64 mtime;      /* The MTIME */
  Blob *pHash;              /* The HASH value */
  int sz;                   /* The SIZE */
  int flags;                /* The FLAGS */
  Blob content;             /* The CONTENT */
  Blob hash;                /* Hash computed from CONTENT to compare with HASH */
  Stmt q;                   /* SQL statements for comparison and insert */
  int isDelete;             /* HASH is "0" indicating this is a delete operation */
  int nullContent;          /* True of CONTENT is NULL */


  pHash = &pXfer->aToken[3];
  if( pXfer->nToken==5
   || !blob_is_filename(&pXfer->aToken[1])
   || !blob_is_int64(&pXfer->aToken[2], &mtime)
   || (blob_eq(pHash,"0")!=0 && !blob_is_uuid(pHash))
   || !blob_is_int(&pXfer->aToken[4], &sz)
   || !blob_is_int(&pXfer->aToken[5], &flags)
  ){
    blob_appendf(&pXfer->err, "malformed uvfile line");
    return;
  }
  blob_init(&content, 0, 0);
................................................................................
  /* The isWriter flag must be true in order to land the new file */
  if( !isWriter ) goto end_accept_unversioned_file;

  /* 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. 
  */
  db_prepare(&q,
    "SELECT mtime, hash FROM unversioned WHERE name=%Q",
    blob_str(&pXfer->aToken[1])
  );
  if( db_step(&q)==SQLITE_ROW ){
    sqlite3_int64 xtime = db_column_int64(&q, 0);
    const char *xhash = db_column_text(&q, 1);
    if( xtime>mtime ){
      db_finalize(&q);
      goto end_accept_unversioned_file;
    }
    if( xhash==0 ) xhash = "0";
    if( xtime==mtime && strcmp(xhash, blob_str(pHash))>0 ){
      db_finalize(&q);
      goto end_accept_unversioned_file;
    }
  }
  db_finalize(&q);
  
  /* Store the content */
  isDelete = blob_eq(pHash, "0");
  if( isDelete ){
    db_prepare(&q,
      "UPDATE unversioned"
      "   SET rcvid=:rcvid, mtime=:mtime, hash=NULL, sz=0, content=NULL"
      " WHERE name=:name"
    );


  }else{
    db_prepare(&q,
      "REPLACE INTO unversioned(name, rcvid, mtime, hash, sz, content)"
      " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
    );
  }
  db_bind_text(&q, ":name", blob_str(&pXfer->aToken[1]));
................................................................................
  db_bind_int(&q, ":sz", blob_size(&content));
  if( !nullContent ){
    blob_compress(&content, &content);
    db_bind_blob(&q, ":content", &content);
  }
  db_step(&q);
  db_finalize(&q);


end_accept_unversioned_file:
  blob_reset(&content);
  blob_reset(&hash);
}

/*
................................................................................
}

/*
** Send the unversioned file identified by zName by generating the
** appropriate "uvfile" card.
**
**     uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT


*/
static void send_unversioned_file(Xfer *pXfer, const char *zName){
  Stmt q1;


  db_static_prepare(&q1,
    "SELECT mtime, hash, encoding, content FROM unversioned WHERE name=%Q",
    zName
  );






  if( db_step(&q1)==SQLITE_ROW ){
    sqlite3_int64 mtime = db_column_int64(&q1, 0);
    const char *zHash = db_column_text(&q1, 1);
    blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime);
    if( zHash==0 ){
      blob_append(pXfer->pOut, " 0 0 1\n", -1);


    }else{
      Blob content;
      blob_init(&content, 0, 0);
      db_column_blob(&q1, 3, &content);
      if( db_column_int(&q1, 2) ){
        blob_uncompress(&content, &content);
      }
      blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content));
      blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content));
      if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
        blob_append(pXfer->pOut, "\n", 1);
................................................................................
    );
    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 = "0"; }
      blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", 
                   zName, mtime, zHash, sz);
    }
    db_finalize(&uvq);
  }
}

................................................................................
    **
    ** Client is requesting an unversioned file.  Send it.
    */
    if( blob_eq(&xfer.aToken[0], "uvgimme")
     && xfer.nToken==2
     && blob_is_filename(&xfer.aToken[1])
    ){
      send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]));
    }else

    /*   igot UUID ?ISPRIVATE?
    **
    ** Client announces that it has a particular file.  If the ISPRIVATE
    ** argument exists and is non-zero, then the file is a private file.
    */
................................................................................
        configure_prepare_to_receive(0);
        recvConfig = 1;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else



    /*    cookie TEXT
    **
    ** A cookie contains a arbitrary-length argument that is server-defined.
    ** The argument must be encoded so as not to contain any whitespace.
    ** The server can optionally send a cookie to the client.  The client
................................................................................
    blob_append(&send, "pragma send-private\n", -1);
  }

  /* When syncing unversioned files, create a TEMP table in which to store
  ** the names of files that do not need to be sent from client to server.
  */
  if( syncFlags & SYNC_UNVERSIONED ){

    db_multi_exec("CREATE TEMP TABLE uv_dont_push(name TEXT PRIMARY KEY)WITHOUT ROWID;");






  }

  /*
  ** Always begin with a clone, pull, or push message
  */
  if( syncFlags & SYNC_CLONE ){
    blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
................................................................................
    /* Send unversioned files present here on the client but missing or
    ** obsolete on the server.
    */
    if( uvDoPush ){
      Stmt uvq;
      assert( (syncFlags & SYNC_UNVERSIONED)!=0 );
      assert( uvStatus==2 );
      db_prepare(&uvq,
        "SELECT name FROM unversioned"
        " WHERE hash IS NOT NULL"
        " EXCEPT "
        "SELECT name FROM uv_dont_send"
      );
      while( db_step(&uvq) ){
        send_unversioned_file(&xfer, db_column_text(&uvq,0));

      }
      db_finalize(&uvq);
      uvDoPush = 0;
    }

    /* Append randomness to the end of the message.  This makes all
    ** messages unique so that that the login-card nonce will always
................................................................................
        }else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
          if( rid ) newPhantom = 1;
        }
        remote_has(rid);
      }else

      /*   uvigot NAME TIMESTAMP HASH SIZE
      **
      ** Server announces that it has a particular unversioned file.  The
      ** server will only send this card if the client had previously sent
      ** a "pragma uv-hash" card with a hash that does not match.
      **
      ** If the identified file needs to be transferred, then do the
      ** transfer.



      */
      if( xfer.nToken==5
       && blob_eq(&xfer.aToken[0], "uvigot")
       && blob_is_filename(&xfer.aToken[1])
       && blob_is_int64(&xfer.aToken[2], &mtime)
       && blob_is_int(&xfer.aToken[4], &size)
       && (size==0 || blob_is_uuid(&xfer.aToken[3]))
      ){



        if( uvStatus==0 ) uvStatus = 2;























      }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.
      */
................................................................................
        ** bandwidth trying to upload unversioned content.  If the server
        ** does accept new unversioned content, it sends "uv-push-ok".
        */
        if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
          uvStatus = 1;
        }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
          uvStatus = 2;

        }
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **







|




|
|
>
>









|

>





|







 







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

|






>
>







 







>







 







>
>

|


>
|
|
|
|
>
>
>
>
>
>






>
>



|







 







|







 







|







 







<







 







>
|
>
>
>
>
>
>







 







|
<
<
<
<
<

|
>







 







|





|
|
>
>
>






|

>
>
>

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







 







>







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
...
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
...
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
...
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
....
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
....
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
....
1426
1427
1428
1429
1430
1431
1432

1433
1434
1435
1436
1437
1438
1439
....
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
....
1835
1836
1837
1838
1839
1840
1841
1842





1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
....
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
....
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
**
** 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
** transmit, or because it is unchanged and this record exists purely
** to update the MTIME.
*/
static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){
  sqlite3_int64 mtime;      /* The MTIME */
  Blob *pHash;              /* The HASH value */
  int sz;                   /* The SIZE */
  int flags;                /* The FLAGS */
  Blob content;             /* The CONTENT */
  Blob hash;                /* Hash computed from CONTENT to compare with HASH */
  Stmt q;                   /* SQL statements for comparison and insert */
  int isDelete;             /* HASH is "-" indicating this is a delete operation */
  int nullContent;          /* True of CONTENT is NULL */
  int iStatus;              /* Result from unversioned_status() */

  pHash = &pXfer->aToken[3];
  if( pXfer->nToken==5
   || !blob_is_filename(&pXfer->aToken[1])
   || !blob_is_int64(&pXfer->aToken[2], &mtime)
   || (blob_eq(pHash,"-")!=0 && !blob_is_uuid(pHash))
   || !blob_is_int(&pXfer->aToken[4], &sz)
   || !blob_is_int(&pXfer->aToken[5], &flags)
  ){
    blob_appendf(&pXfer->err, "malformed uvfile line");
    return;
  }
  blob_init(&content, 0, 0);
................................................................................
  /* The isWriter flag must be true in order to land the new file */
  if( !isWriter ) goto end_accept_unversioned_file;

  /* 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, content=NULL"
      " WHERE name=:name"
    );
  }else if( iStatus==4 ){
    db_prepare(&q, "UPDATE unversioned SET mtime=:mtime WHERE name=:name");
  }else{
    db_prepare(&q,
      "REPLACE INTO unversioned(name, rcvid, mtime, hash, sz, content)"
      " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
    );
  }
  db_bind_text(&q, ":name", blob_str(&pXfer->aToken[1]));
................................................................................
  db_bind_int(&q, ":sz", blob_size(&content));
  if( !nullContent ){
    blob_compress(&content, &content);
    db_bind_blob(&q, ":content", &content);
  }
  db_step(&q);
  db_finalize(&q);
  db_unset("uv-hash", 0);

end_accept_unversioned_file:
  blob_reset(&content);
  blob_reset(&hash);
}

/*
................................................................................
}

/*
** Send the unversioned file identified by zName by generating the
** appropriate "uvfile" card.
**
**     uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
**
** If the noContent flag is set, omit the CONTENT and set the 0x0004 flag in FLAGS.
*/
static void send_unversioned_file(Xfer *pXfer, const char *zName, int noContent){
  Stmt q1;

  if( noContent ){
    db_static_prepare(&q1,
      "SELECT mtime, hash, encoding, sz FROM unversioned WHERE name=%Q",
      zName
    );
  }else{
    db_static_prepare(&q1,
      "SELECT mtime, hash, encoding, sz, content FROM unversioned WHERE name=%Q",
      zName
    );
  }
  if( db_step(&q1)==SQLITE_ROW ){
    sqlite3_int64 mtime = db_column_int64(&q1, 0);
    const char *zHash = db_column_text(&q1, 1);
    blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime);
    if( zHash==0 ){
      blob_append(pXfer->pOut, " 0 0 1\n", -1);
    }else if( noContent ){
      blob_appendf(pXfer->pOut, " %s %d 4\n", zHash, db_column_int(&q1,3));
    }else{
      Blob content;
      blob_init(&content, 0, 0);
      db_column_blob(&q1, 4, &content);
      if( db_column_int(&q1, 2) ){
        blob_uncompress(&content, &content);
      }
      blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content));
      blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content));
      if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
        blob_append(pXfer->pOut, "\n", 1);
................................................................................
    );
    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);
  }
}

................................................................................
    **
    ** Client is requesting an unversioned file.  Send it.
    */
    if( blob_eq(&xfer.aToken[0], "uvgimme")
     && xfer.nToken==2
     && blob_is_filename(&xfer.aToken[1])
    ){
      send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]), 0);
    }else

    /*   igot UUID ?ISPRIVATE?
    **
    ** Client announces that it has a particular file.  If the ISPRIVATE
    ** argument exists and is non-zero, then the file is a private file.
    */
................................................................................
        configure_prepare_to_receive(0);
        recvConfig = 1;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else



    /*    cookie TEXT
    **
    ** A cookie contains a arbitrary-length argument that is server-defined.
    ** The argument must be encoded so as not to contain any whitespace.
    ** The server can optionally send a cookie to the client.  The client
................................................................................
    blob_append(&send, "pragma send-private\n", -1);
  }

  /* When syncing unversioned files, create a TEMP table in which to store
  ** the names of files that do not need to be sent from client to server.
  */
  if( syncFlags & SYNC_UNVERSIONED ){
    db_multi_exec(
       "CREATE TEMP TABLE uv_toSend("
       "  name TEXT PRIMARY KEY,"
       "  mtimeOnly BOOLEAN"
       ") WITHOUT ROWID;"
       "INSERT INTO uv_toSend(name,mtimeOnly)"
       "  SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;"
    );
  }

  /*
  ** Always begin with a clone, pull, or push message
  */
  if( syncFlags & SYNC_CLONE ){
    blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
................................................................................
    /* Send unversioned files present here on the client but missing or
    ** obsolete on the server.
    */
    if( uvDoPush ){
      Stmt uvq;
      assert( (syncFlags & SYNC_UNVERSIONED)!=0 );
      assert( uvStatus==2 );
      db_prepare(&uvq, "SELECT name, mtimeOnly FROM uv_tosend");





      while( db_step(&uvq) ){
        send_unversioned_file(&xfer, db_column_text(&uvq,0), db_column_int(&uvq,1));
        nCardSent++;
      }
      db_finalize(&uvq);
      uvDoPush = 0;
    }

    /* Append randomness to the end of the message.  This makes all
    ** messages unique so that that the login-card nonce will always
................................................................................
        }else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
          if( rid ) newPhantom = 1;
        }
        remote_has(rid);
      }else

      /*   uvigot NAME MTIME HASH SIZE
      **
      ** Server announces that it has a particular unversioned file.  The
      ** server will only send this card if the client had previously sent
      ** a "pragma uv-hash" card with a hash that does not match.
      **
      ** If the identified file needs to be transferred, then setup for the
      ** transfer.  Generate a "uvgimme" card in the reply if the server version
      ** is newer than the client.  Generate a "uvfile" card if the client version
      ** is newer than the server.  If HASH is "-" (indicating that the file has
      ** been deleted) and MTIME is newer, then do the deletion.
      */
      if( xfer.nToken==5
       && blob_eq(&xfer.aToken[0], "uvigot")
       && blob_is_filename(&xfer.aToken[1])
       && blob_is_int64(&xfer.aToken[2], &mtime)
       && blob_is_int(&xfer.aToken[4], &size)
       && (blob_eq(&xfer.aToken[3],"-")==0 || blob_is_uuid(&xfer.aToken[3]))
      ){
        const char *zName = blob_str(&xfer.aToken[1]);
        const char *zHash = blob_str(&xfer.aToken[3]);
        int iStatus;
        if( uvStatus==0 ) uvStatus = 2;
        iStatus = unversioned_status(zName, mtime, zHash);
        if( iStatus<=1 ){
          if( zHash[0]!='-' ){
            @ uvgimme %s(zName)
          }else if( iStatus==1 ){
            db_multi_exec(
               "UPDATE unversioned"
               "   SET mtime=%lld, hash=NULL, sz=0, encoding=0, content=NULL"
               " WHERE name=%Q", mtime, zName
            );
            db_unset("uv-hash", 0);
          }
        }else if( iStatus==2 ){
          db_multi_exec(
            "UPDATE unversioned SET mtime=%lld WHERE name=%Q", mtime, zName
          );
          db_unset("uv-hash", 0);
        }
        if( iStatus<=3 ){
          db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName);
        }else if( iStatus==4 ){
          db_multi_exec("UPDATE uv_tosend SET mtimeOnly=1 WHERE name=%Q", zName);
        }
      }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.
      */
................................................................................
        ** bandwidth trying to upload unversioned content.  If the server
        ** does accept new unversioned content, it sends "uv-push-ok".
        */
        if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
          uvStatus = 1;
        }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
          uvStatus = 2;
          uvDoPush = 1;
        }
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **