| | @@ -282,10 +282,122 @@ |
| 282 | 282 | Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]), |
| 283 | 283 | blob_size(&pXfer->aToken[1])); |
| 284 | 284 | remote_has(rid); |
| 285 | 285 | blob_reset(&content); |
| 286 | 286 | } |
| 287 | + |
| 288 | +/* |
| 289 | +** The aToken[0..nToken-1] blob array is a parse of a "uvfile" line |
| 290 | +** message. This routine finishes parsing that message and adds the |
| 291 | +** unversioned file to the "unversioned" table. |
| 292 | +** |
| 293 | +** The file line is in one of the following two forms: |
| 294 | +** |
| 295 | +** uvfile NAME MTIME HASH SIZE FLAGS |
| 296 | +** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT |
| 297 | +** |
| 298 | +** If the 0x0001 bit of FLAGS is set, that means the file has been |
| 299 | +** deleted, SIZE is zero, the HASH is "0", and the "\n CONTENT" is omitted. |
| 300 | +** |
| 301 | +** SIZE is the number of bytes of CONTENT. The CONTENT is uncompressed. |
| 302 | +** HASH is the SHA1 hash of CONTENT. |
| 303 | +** |
| 304 | +** If the 0x0004 bit of FLAGS is set, that means the CONTENT size |
| 305 | +** is too big to transmit and so the "\n CONTENT" is omitted. |
| 306 | +*/ |
| 307 | +static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){ |
| 308 | + sqlite3_int64 mtime; /* The MTIME */ |
| 309 | + Blob *pHash; /* The HASH value */ |
| 310 | + int sz; /* The SIZE */ |
| 311 | + int flags; /* The FLAGS */ |
| 312 | + Blob content; /* The CONTENT */ |
| 313 | + Blob hash; /* Hash computed from CONTENT to compare with HASH */ |
| 314 | + Stmt q; /* SQL statements for comparison and insert */ |
| 315 | + int isDelete; /* HASH is "0" indicating this is a delete operation */ |
| 316 | + int nullContent; /* True of CONTENT is NULL */ |
| 317 | + |
| 318 | + pHash = &pXfer->aToken[3]; |
| 319 | + if( pXfer->nToken==5 |
| 320 | + || !blob_is_filename(&pXfer->aToken[1]) |
| 321 | + || !blob_is_int64(&pXfer->aToken[2], &mtime) |
| 322 | + || (blob_eq(pHash,"0")!=0 && !blob_is_uuid(pHash)) |
| 323 | + || !blob_is_int(&pXfer->aToken[4], &sz) |
| 324 | + || !blob_is_int(&pXfer->aToken[5], &flags) |
| 325 | + ){ |
| 326 | + blob_appendf(&pXfer->err, "malformed uvfile line"); |
| 327 | + return; |
| 328 | + } |
| 329 | + blob_init(&content, 0, 0); |
| 330 | + blob_init(&hash, 0, 0); |
| 331 | + if( sz>0 && (flags & 0x0005)==0 ){ |
| 332 | + blob_extract(pXfer->pIn, sz, &content); |
| 333 | + nullContent = 0; |
| 334 | + sha1sum_blob(&content, &hash); |
| 335 | + if( blob_compare(&hash, pHash)!=0 ){ |
| 336 | + blob_appendf(&pXfer->err, "in uvfile line, HASH does not match CONTENT"); |
| 337 | + goto end_accept_unversioned_file; |
| 338 | + } |
| 339 | + }else{ |
| 340 | + nullContent = 1; |
| 341 | + } |
| 342 | + |
| 343 | + /* The isWriter flag must be true in order to land the new file */ |
| 344 | + if( !isWriter ) goto end_accept_unversioned_file; |
| 345 | + |
| 346 | + /* Check to see if current content really should be overwritten. Ideally, |
| 347 | + ** a uvfile card should never have been sent unless the overwrite should |
| 348 | + ** occur. But do not trust the sender. Double-check. |
| 349 | + */ |
| 350 | + db_prepare(&q, |
| 351 | + "SELECT mtime, hash FROM unversioned WHERE name=%Q", |
| 352 | + blob_str(&pXfer->aToken[1]) |
| 353 | + ); |
| 354 | + if( db_step(&q)==SQLITE_ROW ){ |
| 355 | + sqlite3_int64 xtime = db_column_int64(&q, 0); |
| 356 | + const char *xhash = db_column_text(&q, 1); |
| 357 | + if( xtime>mtime ){ |
| 358 | + db_finalize(&q); |
| 359 | + goto end_accept_unversioned_file; |
| 360 | + } |
| 361 | + if( xhash==0 ) xhash = "0"; |
| 362 | + if( xtime==mtime && strcmp(xhash, blob_str(pHash))>0 ){ |
| 363 | + db_finalize(&q); |
| 364 | + goto end_accept_unversioned_file; |
| 365 | + } |
| 366 | + } |
| 367 | + db_finalize(&q); |
| 368 | + |
| 369 | + /* Store the content */ |
| 370 | + isDelete = blob_eq(pHash, "0"); |
| 371 | + if( isDelete ){ |
| 372 | + db_prepare(&q, |
| 373 | + "UPDATE unversioned" |
| 374 | + " SET rcvid=:rcvid, mtime=:mtime, hash=NULL, sz=0, content=NULL" |
| 375 | + " WHERE name=:name" |
| 376 | + ); |
| 377 | + }else{ |
| 378 | + db_prepare(&q, |
| 379 | + "REPLACE INTO unversioned(name, rcvid, mtime, hash, sz, content)" |
| 380 | + " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)" |
| 381 | + ); |
| 382 | + } |
| 383 | + db_bind_text(&q, ":name", blob_str(&pXfer->aToken[1])); |
| 384 | + db_bind_int(&q, ":rcvid", g.rcvid); |
| 385 | + db_bind_int64(&q, ":mtime", mtime); |
| 386 | + db_bind_text(&q, ":hash", blob_str(&pXfer->aToken[5])); |
| 387 | + db_bind_int(&q, ":sz", blob_size(&content)); |
| 388 | + if( !nullContent ){ |
| 389 | + blob_compress(&content, &content); |
| 390 | + db_bind_blob(&q, ":content", &content); |
| 391 | + } |
| 392 | + db_step(&q); |
| 393 | + db_finalize(&q); |
| 394 | + |
| 395 | +end_accept_unversioned_file: |
| 396 | + blob_reset(&content); |
| 397 | + blob_reset(&hash); |
| 398 | +} |
| 287 | 399 | |
| 288 | 400 | /* |
| 289 | 401 | ** Try to send a file as a delta against its parent. |
| 290 | 402 | ** If successful, return the number of bytes in the delta. |
| 291 | 403 | ** If we cannot generate an appropriate delta, then send |
| | @@ -524,10 +636,47 @@ |
| 524 | 636 | blob_reset(&fullContent); |
| 525 | 637 | } |
| 526 | 638 | } |
| 527 | 639 | db_reset(&q1); |
| 528 | 640 | } |
| 641 | + |
| 642 | +/* |
| 643 | +** Send the unversioned file identified by zName by generating the |
| 644 | +** appropriate "uvfile" card. |
| 645 | +** |
| 646 | +** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT |
| 647 | +*/ |
| 648 | +static void send_unversioned_file(Xfer *pXfer, const char *zName){ |
| 649 | + Stmt q1; |
| 650 | + |
| 651 | + db_static_prepare(&q1, |
| 652 | + "SELECT mtime, hash, encoding, content FROM unversioned WHERE name=%Q", |
| 653 | + zName |
| 654 | + ); |
| 655 | + if( db_step(&q1)==SQLITE_ROW ){ |
| 656 | + sqlite3_int64 mtime = db_column_int64(&q1, 0); |
| 657 | + const char *zHash = db_column_text(&q1, 1); |
| 658 | + blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime); |
| 659 | + if( zHash==0 ){ |
| 660 | + blob_append(pXfer->pOut, " 0 0 1\n", -1); |
| 661 | + }else{ |
| 662 | + Blob content; |
| 663 | + blob_init(&content, 0, 0); |
| 664 | + db_column_blob(&q1, 3, &content); |
| 665 | + if( db_column_int(&q1, 2) ){ |
| 666 | + blob_uncompress(&content, &content); |
| 667 | + } |
| 668 | + blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content)); |
| 669 | + blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content)); |
| 670 | + if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){ |
| 671 | + blob_append(pXfer->pOut, "\n", 1); |
| 672 | + } |
| 673 | + blob_reset(&content); |
| 674 | + } |
| 675 | + } |
| 676 | + db_finalize(&q1); |
| 677 | +} |
| 529 | 678 | |
| 530 | 679 | /* |
| 531 | 680 | ** Send a gimme message for every phantom. |
| 532 | 681 | ** |
| 533 | 682 | ** Except: do not request shunned artifacts. And do not request |
| | @@ -833,10 +982,40 @@ |
| 833 | 982 | blob_size(&content), blob_str(&content)); |
| 834 | 983 | blob_reset(&content); |
| 835 | 984 | } |
| 836 | 985 | } |
| 837 | 986 | |
| 987 | + |
| 988 | +/* |
| 989 | +** pXfer is a "pragma uv-hash HASH" card. |
| 990 | +** |
| 991 | +** If HASH is different from the unversioned content hash on this server, |
| 992 | +** then send a bunch of uvigot cards, one for each entry unversioned file |
| 993 | +** on this server. |
| 994 | +*/ |
| 995 | +static void send_unversioned_catalog(Xfer *pXfer){ |
| 996 | + unversioned_schema(); |
| 997 | + if( !blob_eq(&pXfer->aToken[2], unversioned_content_hash(0)) ){ |
| 998 | + int nUvIgot = 0; |
| 999 | + Stmt uvq; |
| 1000 | + db_prepare(&uvq, |
| 1001 | + "SELECT name, mtime, hash, sz FROM unversioned" |
| 1002 | + ); |
| 1003 | + while( db_step(&uvq)==SQLITE_ROW ){ |
| 1004 | + const char *zName = db_column_text(&uvq,0); |
| 1005 | + sqlite3_int64 mtime = db_column_int64(&uvq,1); |
| 1006 | + const char *zHash = db_column_text(&uvq,2); |
| 1007 | + int sz = db_column_int(&uvq,3); |
| 1008 | + nUvIgot++; |
| 1009 | + if( zHash==0 ){ sz = 0; zHash = "0"; } |
| 1010 | + blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", |
| 1011 | + zName, mtime, zHash, sz); |
| 1012 | + } |
| 1013 | + db_finalize(&uvq); |
| 1014 | + } |
| 1015 | +} |
| 1016 | + |
| 838 | 1017 | /* |
| 839 | 1018 | ** Called when there is an attempt to transfer private content to and |
| 840 | 1019 | ** from a server without authorization. |
| 841 | 1020 | */ |
| 842 | 1021 | static void server_private_xfer_not_authorized(void){ |
| | @@ -1026,10 +1205,24 @@ |
| 1026 | 1205 | @ error %T(blob_str(&xfer.err)) |
| 1027 | 1206 | nErr++; |
| 1028 | 1207 | break; |
| 1029 | 1208 | } |
| 1030 | 1209 | }else |
| 1210 | + |
| 1211 | + /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT |
| 1212 | + ** |
| 1213 | + ** Accept an unversioned file from the client. |
| 1214 | + */ |
| 1215 | + if( blob_eq(&xfer.aToken[0], "uvfile") ){ |
| 1216 | + xfer_accept_unversioned_file(&xfer, g.perm.Write); |
| 1217 | + if( blob_size(&xfer.err) ){ |
| 1218 | + cgi_reset_content(); |
| 1219 | + @ error %T(blob_str(&xfer.err)) |
| 1220 | + nErr++; |
| 1221 | + break; |
| 1222 | + } |
| 1223 | + }else |
| 1031 | 1224 | |
| 1032 | 1225 | /* gimme UUID |
| 1033 | 1226 | ** |
| 1034 | 1227 | ** Client is requesting a file. Send it. |
| 1035 | 1228 | */ |
| | @@ -1043,10 +1236,21 @@ |
| 1043 | 1236 | if( rid ){ |
| 1044 | 1237 | send_file(&xfer, rid, &xfer.aToken[1], deltaFlag); |
| 1045 | 1238 | } |
| 1046 | 1239 | } |
| 1047 | 1240 | }else |
| 1241 | + |
| 1242 | + /* uvgimme NAME |
| 1243 | + ** |
| 1244 | + ** Client is requesting an unversioned file. Send it. |
| 1245 | + */ |
| 1246 | + if( blob_eq(&xfer.aToken[0], "uvgimme") |
| 1247 | + && xfer.nToken==2 |
| 1248 | + && blob_is_filename(&xfer.aToken[1]) |
| 1249 | + ){ |
| 1250 | + send_unversioned_file(&xfer, blob_str(&xfer.aToken[1])); |
| 1251 | + }else |
| 1048 | 1252 | |
| 1049 | 1253 | /* igot UUID ?ISPRIVATE? |
| 1050 | 1254 | ** |
| 1051 | 1255 | ** Client announces that it has a particular file. If the ISPRIVATE |
| 1052 | 1256 | ** argument exists and is non-zero, then the file is a private file. |
| | @@ -1269,10 +1473,11 @@ |
| 1269 | 1473 | ** The client issue pragmas to try to influence the behavior of the |
| 1270 | 1474 | ** server. These are requests only. Unknown pragmas are silently |
| 1271 | 1475 | ** ignored. |
| 1272 | 1476 | */ |
| 1273 | 1477 | if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){ |
| 1478 | + |
| 1274 | 1479 | /* pragma send-private |
| 1275 | 1480 | ** |
| 1276 | 1481 | ** If the user has the "x" privilege (which must be set explicitly - |
| 1277 | 1482 | ** it is not automatic with "a" or "s") then this pragma causes |
| 1278 | 1483 | ** private information to be pulled in addition to public records. |
| | @@ -1283,17 +1488,34 @@ |
| 1283 | 1488 | server_private_xfer_not_authorized(); |
| 1284 | 1489 | }else{ |
| 1285 | 1490 | xfer.syncPrivate = 1; |
| 1286 | 1491 | } |
| 1287 | 1492 | } |
| 1493 | + |
| 1288 | 1494 | /* pragma send-catalog |
| 1289 | 1495 | ** |
| 1290 | 1496 | ** Send igot cards for all known artifacts. |
| 1291 | 1497 | */ |
| 1292 | 1498 | if( blob_eq(&xfer.aToken[1], "send-catalog") ){ |
| 1293 | 1499 | xfer.resync = 0x7fffffff; |
| 1294 | 1500 | } |
| 1501 | + |
| 1502 | + /* pragma uv-hash HASH |
| 1503 | + ** |
| 1504 | + ** The client wants to make sure that unversioned files are all synced. |
| 1505 | + ** If the HASH does not match, send a complete catalog of |
| 1506 | + ** "uvigot" cards. |
| 1507 | + */ |
| 1508 | + if( blob_eq(&xfer.aToken[1], "uv-hash") && blob_is_uuid(&xfer.aToken[2]) ){ |
| 1509 | + if( g.perm.Read && g.perm.Write ){ |
| 1510 | + @ pragma uv-push-ok |
| 1511 | + send_unversioned_catalog(&xfer); |
| 1512 | + }else if( g.perm.Read ){ |
| 1513 | + @ pragma uv-pull-only |
| 1514 | + send_unversioned_catalog(&xfer); |
| 1515 | + } |
| 1516 | + } |
| 1295 | 1517 | }else |
| 1296 | 1518 | |
| 1297 | 1519 | /* Unknown message |
| 1298 | 1520 | */ |
| 1299 | 1521 | { |
| | @@ -1391,16 +1613,17 @@ |
| 1391 | 1613 | |
| 1392 | 1614 | #if INTERFACE |
| 1393 | 1615 | /* |
| 1394 | 1616 | ** Flag options for controlling client_sync() |
| 1395 | 1617 | */ |
| 1396 | | -#define SYNC_PUSH 0x0001 |
| 1397 | | -#define SYNC_PULL 0x0002 |
| 1398 | | -#define SYNC_CLONE 0x0004 |
| 1399 | | -#define SYNC_PRIVATE 0x0008 |
| 1400 | | -#define SYNC_VERBOSE 0x0010 |
| 1401 | | -#define SYNC_RESYNC 0x0020 |
| 1618 | +#define SYNC_PUSH 0x0001 |
| 1619 | +#define SYNC_PULL 0x0002 |
| 1620 | +#define SYNC_CLONE 0x0004 |
| 1621 | +#define SYNC_PRIVATE 0x0008 |
| 1622 | +#define SYNC_VERBOSE 0x0010 |
| 1623 | +#define SYNC_RESYNC 0x0020 |
| 1624 | +#define SYNC_UNVERSIONED 0x0040 |
| 1402 | 1625 | #endif |
| 1403 | 1626 | |
| 1404 | 1627 | /* |
| 1405 | 1628 | ** Floating-point absolute value |
| 1406 | 1629 | */ |
| | @@ -1423,11 +1646,11 @@ |
| 1423 | 1646 | ){ |
| 1424 | 1647 | int go = 1; /* Loop until zero */ |
| 1425 | 1648 | int nCardSent = 0; /* Number of cards sent */ |
| 1426 | 1649 | int nCardRcvd = 0; /* Number of cards received */ |
| 1427 | 1650 | int nCycle = 0; /* Number of round trips to the server */ |
| 1428 | | - int size; /* Size of a config value */ |
| 1651 | + int size; /* Size of a config value or uvfile */ |
| 1429 | 1652 | int origConfigRcvMask; /* Original value of configRcvMask */ |
| 1430 | 1653 | int nFileRecv; /* Number of files received */ |
| 1431 | 1654 | int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ |
| 1432 | 1655 | const char *zCookie; /* Server cookie */ |
| 1433 | 1656 | i64 nSent, nRcvd; /* Bytes sent and received (after compression) */ |
| | @@ -1444,13 +1667,17 @@ |
| 1444 | 1667 | int nRoundtrip= 0; /* Number of HTTP requests */ |
| 1445 | 1668 | int nArtifactSent = 0; /* Total artifacts sent */ |
| 1446 | 1669 | int nArtifactRcvd = 0; /* Total artifacts received */ |
| 1447 | 1670 | const char *zOpType = 0;/* Push, Pull, Sync, Clone */ |
| 1448 | 1671 | double rSkew = 0.0; /* Maximum time skew */ |
| 1672 | + int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */ |
| 1673 | + int uvStatus = 0; /* 0: no I/O. 1: pull-only 2: push-and-pull */ |
| 1674 | + int uvDoPush = 0; /* If true, generate uvfile messages to send to server */ |
| 1675 | + sqlite3_int64 mtime; /* Modification time on a UV file */ |
| 1449 | 1676 | |
| 1450 | 1677 | if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH; |
| 1451 | | - if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0 |
| 1678 | + if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED))==0 |
| 1452 | 1679 | && configRcvMask==0 && configSendMask==0 ) return 0; |
| 1453 | 1680 | |
| 1454 | 1681 | transport_stats(0, 0, 1); |
| 1455 | 1682 | socket_global_init(); |
| 1456 | 1683 | memset(&xfer, 0, sizeof(xfer)); |
| | @@ -1473,10 +1700,17 @@ |
| 1473 | 1700 | |
| 1474 | 1701 | /* Send the send-private pragma if we are trying to sync private data */ |
| 1475 | 1702 | if( syncFlags & SYNC_PRIVATE ){ |
| 1476 | 1703 | blob_append(&send, "pragma send-private\n", -1); |
| 1477 | 1704 | } |
| 1705 | + |
| 1706 | + /* When syncing unversioned files, create a TEMP table in which to store |
| 1707 | + ** the names of files that do not need to be sent from client to server. |
| 1708 | + */ |
| 1709 | + if( syncFlags & SYNC_UNVERSIONED ){ |
| 1710 | + db_multi_exec("CREATE TEMP TABLE uv_dont_push(name TEXT PRIMARY KEY)WITHOUT ROWID;"); |
| 1711 | + } |
| 1478 | 1712 | |
| 1479 | 1713 | /* |
| 1480 | 1714 | ** Always begin with a clone, pull, or push message |
| 1481 | 1715 | */ |
| 1482 | 1716 | if( syncFlags & SYNC_CLONE ){ |
| | @@ -1514,11 +1748,11 @@ |
| 1514 | 1748 | db_multi_exec( |
| 1515 | 1749 | "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" |
| 1516 | 1750 | ); |
| 1517 | 1751 | manifest_crosslink_begin(); |
| 1518 | 1752 | |
| 1519 | | - /* Send make the most recently received cookie. Let the server |
| 1753 | + /* Send back the most recently received cookie. Let the server |
| 1520 | 1754 | ** figure out if this is a cookie that it cares about. |
| 1521 | 1755 | */ |
| 1522 | 1756 | zCookie = db_get("cookie", 0); |
| 1523 | 1757 | if( zCookie ){ |
| 1524 | 1758 | blob_appendf(&send, "cookie %s\n", zCookie); |
| | @@ -1558,10 +1792,23 @@ |
| 1558 | 1792 | configure_prepare_to_receive(overwrite); |
| 1559 | 1793 | } |
| 1560 | 1794 | origConfigRcvMask = configRcvMask; |
| 1561 | 1795 | configRcvMask = 0; |
| 1562 | 1796 | } |
| 1797 | + |
| 1798 | + /* Send a request to sync unversioned files. On a clone, delay sending |
| 1799 | + ** this until the second cycle since the login card might fail on |
| 1800 | + ** the first cycle. |
| 1801 | + */ |
| 1802 | + if( (syncFlags & SYNC_UNVERSIONED)!=0 |
| 1803 | + && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) |
| 1804 | + && !uvHashSent |
| 1805 | + ){ |
| 1806 | + blob_appendf(&send, "pragma uv-hash %s\n", unversioned_content_hash(0)); |
| 1807 | + nCardSent++; |
| 1808 | + uvHashSent = 1; |
| 1809 | + } |
| 1563 | 1810 | |
| 1564 | 1811 | /* Send configuration parameters being pushed */ |
| 1565 | 1812 | if( configSendMask ){ |
| 1566 | 1813 | if( zOpType==0 ) zOpType = "Push"; |
| 1567 | 1814 | if( configSendMask & CONFIGSET_OLDFORMAT ){ |
| | @@ -1575,10 +1822,30 @@ |
| 1575 | 1822 | }else{ |
| 1576 | 1823 | nCardSent += configure_send_group(xfer.pOut, configSendMask, 0); |
| 1577 | 1824 | } |
| 1578 | 1825 | configSendMask = 0; |
| 1579 | 1826 | } |
| 1827 | + |
| 1828 | + /* Send unversioned files present here on the client but missing or |
| 1829 | + ** obsolete on the server. |
| 1830 | + */ |
| 1831 | + if( uvDoPush ){ |
| 1832 | + Stmt uvq; |
| 1833 | + assert( (syncFlags & SYNC_UNVERSIONED)!=0 ); |
| 1834 | + assert( uvStatus==2 ); |
| 1835 | + db_prepare(&uvq, |
| 1836 | + "SELECT name FROM unversioned" |
| 1837 | + " WHERE hash IS NOT NULL" |
| 1838 | + " EXCEPT " |
| 1839 | + "SELECT name FROM uv_dont_send" |
| 1840 | + ); |
| 1841 | + while( db_step(&uvq) ){ |
| 1842 | + send_unversioned_file(&xfer, db_column_text(&uvq,0)); |
| 1843 | + } |
| 1844 | + db_finalize(&uvq); |
| 1845 | + uvDoPush = 0; |
| 1846 | + } |
| 1580 | 1847 | |
| 1581 | 1848 | /* Append randomness to the end of the message. This makes all |
| 1582 | 1849 | ** messages unique so that that the login-card nonce will always |
| 1583 | 1850 | ** be unique. |
| 1584 | 1851 | */ |
| | @@ -1683,10 +1950,18 @@ |
| 1683 | 1950 | */ |
| 1684 | 1951 | if( blob_eq(&xfer.aToken[0],"cfile") ){ |
| 1685 | 1952 | xfer_accept_compressed_file(&xfer, 0, 0); |
| 1686 | 1953 | nArtifactRcvd++; |
| 1687 | 1954 | }else |
| 1955 | + |
| 1956 | + /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT |
| 1957 | + ** |
| 1958 | + ** Accept an unversioned file from the client. |
| 1959 | + */ |
| 1960 | + if( blob_eq(&xfer.aToken[0], "uvfile") ){ |
| 1961 | + xfer_accept_unversioned_file(&xfer, 1); |
| 1962 | + }else |
| 1688 | 1963 | |
| 1689 | 1964 | /* gimme UUID |
| 1690 | 1965 | ** |
| 1691 | 1966 | ** Server is requesting a file. If the file is a manifest, assume |
| 1692 | 1967 | ** that the server will also want to know all of the content files |
| | @@ -1730,10 +2005,28 @@ |
| 1730 | 2005 | if( rid ) newPhantom = 1; |
| 1731 | 2006 | } |
| 1732 | 2007 | remote_has(rid); |
| 1733 | 2008 | }else |
| 1734 | 2009 | |
| 2010 | + /* uvigot NAME TIMESTAMP HASH SIZE |
| 2011 | + ** |
| 2012 | + ** Server announces that it has a particular unversioned file. The |
| 2013 | + ** server will only send this card if the client had previously sent |
| 2014 | + ** a "pragma uv-hash" card with a hash that does not match. |
| 2015 | + ** |
| 2016 | + ** If the identified file needs to be transferred, then do the |
| 2017 | + ** transfer. |
| 2018 | + */ |
| 2019 | + if( xfer.nToken==5 |
| 2020 | + && blob_eq(&xfer.aToken[0], "uvigot") |
| 2021 | + && blob_is_filename(&xfer.aToken[1]) |
| 2022 | + && blob_is_int64(&xfer.aToken[2], &mtime) |
| 2023 | + && blob_is_int(&xfer.aToken[4], &size) |
| 2024 | + && (size==0 || blob_is_uuid(&xfer.aToken[3])) |
| 2025 | + ){ |
| 2026 | + if( uvStatus==0 ) uvStatus = 2; |
| 2027 | + }else |
| 1735 | 2028 | |
| 1736 | 2029 | /* push SERVERCODE PRODUCTCODE |
| 1737 | 2030 | ** |
| 1738 | 2031 | ** Should only happen in response to a clone. This message tells |
| 1739 | 2032 | ** the client what product to use for the new database. |
| | @@ -1833,10 +2126,21 @@ |
| 1833 | 2126 | ** The server can send pragmas to try to convey meta-information to |
| 1834 | 2127 | ** the client. These are informational only. Unknown pragmas are |
| 1835 | 2128 | ** silently ignored. |
| 1836 | 2129 | */ |
| 1837 | 2130 | if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){ |
| 2131 | + /* If the server is unwill to accept new unversioned content (because |
| 2132 | + ** this client lacks the necessary permissions) then it sends a |
| 2133 | + ** "uv-pull-only" pragma so that the client will know not to waste |
| 2134 | + ** bandwidth trying to upload unversioned content. If the server |
| 2135 | + ** does accept new unversioned content, it sends "uv-push-ok". |
| 2136 | + */ |
| 2137 | + if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){ |
| 2138 | + uvStatus = 1; |
| 2139 | + }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){ |
| 2140 | + uvStatus = 2; |
| 2141 | + } |
| 1838 | 2142 | }else |
| 1839 | 2143 | |
| 1840 | 2144 | /* error MESSAGE |
| 1841 | 2145 | ** |
| 1842 | 2146 | ** Report an error and abandon the sync session. |
| | @@ -1931,11 +2235,11 @@ |
| 1931 | 2235 | xfer.nDanglingFile = 0; |
| 1932 | 2236 | |
| 1933 | 2237 | /* If we have one or more files queued to send, then go |
| 1934 | 2238 | ** another round |
| 1935 | 2239 | */ |
| 1936 | | - if( xfer.nFileSent+xfer.nDeltaSent>0 ){ |
| 2240 | + if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){ |
| 1937 | 2241 | go = 1; |
| 1938 | 2242 | } |
| 1939 | 2243 | |
| 1940 | 2244 | /* If this is a clone, the go at least two rounds */ |
| 1941 | 2245 | if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1; |
| 1942 | 2246 | |