| | @@ -294,34 +294,37 @@ |
| 294 | 294 | ** |
| 295 | 295 | ** uvfile NAME MTIME HASH SIZE FLAGS |
| 296 | 296 | ** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT |
| 297 | 297 | ** |
| 298 | 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. |
| 299 | +** deleted, SIZE is zero, the HASH is "-", and the "\n CONTENT" is omitted. |
| 300 | 300 | ** |
| 301 | 301 | ** SIZE is the number of bytes of CONTENT. The CONTENT is uncompressed. |
| 302 | 302 | ** HASH is the SHA1 hash of CONTENT. |
| 303 | 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. |
| 304 | +** If the 0x0004 bit of FLAGS is set, that means the CONTENT is omitted. |
| 305 | +** The sender might have omitted the content because it is too big to |
| 306 | +** transmit, or because it is unchanged and this record exists purely |
| 307 | +** to update the MTIME. |
| 306 | 308 | */ |
| 307 | 309 | static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){ |
| 308 | 310 | sqlite3_int64 mtime; /* The MTIME */ |
| 309 | 311 | Blob *pHash; /* The HASH value */ |
| 310 | 312 | int sz; /* The SIZE */ |
| 311 | 313 | int flags; /* The FLAGS */ |
| 312 | 314 | Blob content; /* The CONTENT */ |
| 313 | 315 | Blob hash; /* Hash computed from CONTENT to compare with HASH */ |
| 314 | 316 | Stmt q; /* SQL statements for comparison and insert */ |
| 315 | | - int isDelete; /* HASH is "0" indicating this is a delete operation */ |
| 317 | + int isDelete; /* HASH is "-" indicating this is a delete operation */ |
| 316 | 318 | int nullContent; /* True of CONTENT is NULL */ |
| 319 | + int iStatus; /* Result from unversioned_status() */ |
| 317 | 320 | |
| 318 | 321 | pHash = &pXfer->aToken[3]; |
| 319 | 322 | if( pXfer->nToken==5 |
| 320 | 323 | || !blob_is_filename(&pXfer->aToken[1]) |
| 321 | 324 | || !blob_is_int64(&pXfer->aToken[2], &mtime) |
| 322 | | - || (blob_eq(pHash,"0")!=0 && !blob_is_uuid(pHash)) |
| 325 | + || (blob_eq(pHash,"-")!=0 && !blob_is_uuid(pHash)) |
| 323 | 326 | || !blob_is_int(&pXfer->aToken[4], &sz) |
| 324 | 327 | || !blob_is_int(&pXfer->aToken[5], &flags) |
| 325 | 328 | ){ |
| 326 | 329 | blob_appendf(&pXfer->err, "malformed uvfile line"); |
| 327 | 330 | return; |
| | @@ -345,37 +348,23 @@ |
| 345 | 348 | |
| 346 | 349 | /* Check to see if current content really should be overwritten. Ideally, |
| 347 | 350 | ** a uvfile card should never have been sent unless the overwrite should |
| 348 | 351 | ** occur. But do not trust the sender. Double-check. |
| 349 | 352 | */ |
| 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); |
| 353 | + iStatus = unversioned_status(blob_str(&pXfer->aToken[1]), mtime, blob_str(pHash)); |
| 354 | + if( iStatus>=3 ) goto end_accept_unversioned_file; |
| 368 | 355 | |
| 369 | 356 | /* Store the content */ |
| 370 | | - isDelete = blob_eq(pHash, "0"); |
| 357 | + isDelete = blob_eq(pHash, "-"); |
| 371 | 358 | if( isDelete ){ |
| 372 | 359 | db_prepare(&q, |
| 373 | 360 | "UPDATE unversioned" |
| 374 | 361 | " SET rcvid=:rcvid, mtime=:mtime, hash=NULL, sz=0, content=NULL" |
| 375 | 362 | " WHERE name=:name" |
| 376 | 363 | ); |
| 364 | + }else if( iStatus==4 ){ |
| 365 | + db_prepare(&q, "UPDATE unversioned SET mtime=:mtime WHERE name=:name"); |
| 377 | 366 | }else{ |
| 378 | 367 | db_prepare(&q, |
| 379 | 368 | "REPLACE INTO unversioned(name, rcvid, mtime, hash, sz, content)" |
| 380 | 369 | " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)" |
| 381 | 370 | ); |
| | @@ -389,10 +378,11 @@ |
| 389 | 378 | blob_compress(&content, &content); |
| 390 | 379 | db_bind_blob(&q, ":content", &content); |
| 391 | 380 | } |
| 392 | 381 | db_step(&q); |
| 393 | 382 | db_finalize(&q); |
| 383 | + db_unset("uv-hash", 0); |
| 394 | 384 | |
| 395 | 385 | end_accept_unversioned_file: |
| 396 | 386 | blob_reset(&content); |
| 397 | 387 | blob_reset(&hash); |
| 398 | 388 | } |
| | @@ -642,28 +632,39 @@ |
| 642 | 632 | /* |
| 643 | 633 | ** Send the unversioned file identified by zName by generating the |
| 644 | 634 | ** appropriate "uvfile" card. |
| 645 | 635 | ** |
| 646 | 636 | ** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT |
| 637 | +** |
| 638 | +** If the noContent flag is set, omit the CONTENT and set the 0x0004 flag in FLAGS. |
| 647 | 639 | */ |
| 648 | | -static void send_unversioned_file(Xfer *pXfer, const char *zName){ |
| 640 | +static void send_unversioned_file(Xfer *pXfer, const char *zName, int noContent){ |
| 649 | 641 | Stmt q1; |
| 650 | 642 | |
| 651 | | - db_static_prepare(&q1, |
| 652 | | - "SELECT mtime, hash, encoding, content FROM unversioned WHERE name=%Q", |
| 653 | | - zName |
| 654 | | - ); |
| 643 | + if( noContent ){ |
| 644 | + db_static_prepare(&q1, |
| 645 | + "SELECT mtime, hash, encoding, sz FROM unversioned WHERE name=%Q", |
| 646 | + zName |
| 647 | + ); |
| 648 | + }else{ |
| 649 | + db_static_prepare(&q1, |
| 650 | + "SELECT mtime, hash, encoding, sz, content FROM unversioned WHERE name=%Q", |
| 651 | + zName |
| 652 | + ); |
| 653 | + } |
| 655 | 654 | if( db_step(&q1)==SQLITE_ROW ){ |
| 656 | 655 | sqlite3_int64 mtime = db_column_int64(&q1, 0); |
| 657 | 656 | const char *zHash = db_column_text(&q1, 1); |
| 658 | 657 | blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime); |
| 659 | 658 | if( zHash==0 ){ |
| 660 | 659 | blob_append(pXfer->pOut, " 0 0 1\n", -1); |
| 660 | + }else if( noContent ){ |
| 661 | + blob_appendf(pXfer->pOut, " %s %d 4\n", zHash, db_column_int(&q1,3)); |
| 661 | 662 | }else{ |
| 662 | 663 | Blob content; |
| 663 | 664 | blob_init(&content, 0, 0); |
| 664 | | - db_column_blob(&q1, 3, &content); |
| 665 | + db_column_blob(&q1, 4, &content); |
| 665 | 666 | if( db_column_int(&q1, 2) ){ |
| 666 | 667 | blob_uncompress(&content, &content); |
| 667 | 668 | } |
| 668 | 669 | blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content)); |
| 669 | 670 | blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content)); |
| | @@ -1004,11 +1005,11 @@ |
| 1004 | 1005 | const char *zName = db_column_text(&uvq,0); |
| 1005 | 1006 | sqlite3_int64 mtime = db_column_int64(&uvq,1); |
| 1006 | 1007 | const char *zHash = db_column_text(&uvq,2); |
| 1007 | 1008 | int sz = db_column_int(&uvq,3); |
| 1008 | 1009 | nUvIgot++; |
| 1009 | | - if( zHash==0 ){ sz = 0; zHash = "0"; } |
| 1010 | + if( zHash==0 ){ sz = 0; zHash = "-"; } |
| 1010 | 1011 | blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", |
| 1011 | 1012 | zName, mtime, zHash, sz); |
| 1012 | 1013 | } |
| 1013 | 1014 | db_finalize(&uvq); |
| 1014 | 1015 | } |
| | @@ -1245,11 +1246,11 @@ |
| 1245 | 1246 | */ |
| 1246 | 1247 | if( blob_eq(&xfer.aToken[0], "uvgimme") |
| 1247 | 1248 | && xfer.nToken==2 |
| 1248 | 1249 | && blob_is_filename(&xfer.aToken[1]) |
| 1249 | 1250 | ){ |
| 1250 | | - send_unversioned_file(&xfer, blob_str(&xfer.aToken[1])); |
| 1251 | + send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]), 0); |
| 1251 | 1252 | }else |
| 1252 | 1253 | |
| 1253 | 1254 | /* igot UUID ?ISPRIVATE? |
| 1254 | 1255 | ** |
| 1255 | 1256 | ** Client announces that it has a particular file. If the ISPRIVATE |
| | @@ -1427,11 +1428,10 @@ |
| 1427 | 1428 | } |
| 1428 | 1429 | configure_receive(zName, &content, CONFIGSET_ALL); |
| 1429 | 1430 | blob_reset(&content); |
| 1430 | 1431 | blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR); |
| 1431 | 1432 | }else |
| 1432 | | - |
| 1433 | 1433 | |
| 1434 | 1434 | |
| 1435 | 1435 | /* cookie TEXT |
| 1436 | 1436 | ** |
| 1437 | 1437 | ** A cookie contains a arbitrary-length argument that is server-defined. |
| | @@ -1705,11 +1705,18 @@ |
| 1705 | 1705 | |
| 1706 | 1706 | /* When syncing unversioned files, create a TEMP table in which to store |
| 1707 | 1707 | ** the names of files that do not need to be sent from client to server. |
| 1708 | 1708 | */ |
| 1709 | 1709 | if( syncFlags & SYNC_UNVERSIONED ){ |
| 1710 | | - db_multi_exec("CREATE TEMP TABLE uv_dont_push(name TEXT PRIMARY KEY)WITHOUT ROWID;"); |
| 1710 | + db_multi_exec( |
| 1711 | + "CREATE TEMP TABLE uv_toSend(" |
| 1712 | + " name TEXT PRIMARY KEY," |
| 1713 | + " mtimeOnly BOOLEAN" |
| 1714 | + ") WITHOUT ROWID;" |
| 1715 | + "INSERT INTO uv_toSend(name,mtimeOnly)" |
| 1716 | + " SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;" |
| 1717 | + ); |
| 1711 | 1718 | } |
| 1712 | 1719 | |
| 1713 | 1720 | /* |
| 1714 | 1721 | ** Always begin with a clone, pull, or push message |
| 1715 | 1722 | */ |
| | @@ -1830,18 +1837,14 @@ |
| 1830 | 1837 | */ |
| 1831 | 1838 | if( uvDoPush ){ |
| 1832 | 1839 | Stmt uvq; |
| 1833 | 1840 | assert( (syncFlags & SYNC_UNVERSIONED)!=0 ); |
| 1834 | 1841 | 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 | | - ); |
| 1842 | + db_prepare(&uvq, "SELECT name, mtimeOnly FROM uv_tosend"); |
| 1841 | 1843 | while( db_step(&uvq) ){ |
| 1842 | | - send_unversioned_file(&xfer, db_column_text(&uvq,0)); |
| 1844 | + send_unversioned_file(&xfer, db_column_text(&uvq,0), db_column_int(&uvq,1)); |
| 1845 | + nCardSent++; |
| 1843 | 1846 | } |
| 1844 | 1847 | db_finalize(&uvq); |
| 1845 | 1848 | uvDoPush = 0; |
| 1846 | 1849 | } |
| 1847 | 1850 | |
| | @@ -2005,27 +2008,56 @@ |
| 2005 | 2008 | if( rid ) newPhantom = 1; |
| 2006 | 2009 | } |
| 2007 | 2010 | remote_has(rid); |
| 2008 | 2011 | }else |
| 2009 | 2012 | |
| 2010 | | - /* uvigot NAME TIMESTAMP HASH SIZE |
| 2013 | + /* uvigot NAME MTIME HASH SIZE |
| 2011 | 2014 | ** |
| 2012 | 2015 | ** Server announces that it has a particular unversioned file. The |
| 2013 | 2016 | ** server will only send this card if the client had previously sent |
| 2014 | 2017 | ** a "pragma uv-hash" card with a hash that does not match. |
| 2015 | 2018 | ** |
| 2016 | | - ** If the identified file needs to be transferred, then do the |
| 2017 | | - ** transfer. |
| 2019 | + ** If the identified file needs to be transferred, then setup for the |
| 2020 | + ** transfer. Generate a "uvgimme" card in the reply if the server version |
| 2021 | + ** is newer than the client. Generate a "uvfile" card if the client version |
| 2022 | + ** is newer than the server. If HASH is "-" (indicating that the file has |
| 2023 | + ** been deleted) and MTIME is newer, then do the deletion. |
| 2018 | 2024 | */ |
| 2019 | 2025 | if( xfer.nToken==5 |
| 2020 | 2026 | && blob_eq(&xfer.aToken[0], "uvigot") |
| 2021 | 2027 | && blob_is_filename(&xfer.aToken[1]) |
| 2022 | 2028 | && blob_is_int64(&xfer.aToken[2], &mtime) |
| 2023 | 2029 | && blob_is_int(&xfer.aToken[4], &size) |
| 2024 | | - && (size==0 || blob_is_uuid(&xfer.aToken[3])) |
| 2030 | + && (blob_eq(&xfer.aToken[3],"-")==0 || blob_is_uuid(&xfer.aToken[3])) |
| 2025 | 2031 | ){ |
| 2032 | + const char *zName = blob_str(&xfer.aToken[1]); |
| 2033 | + const char *zHash = blob_str(&xfer.aToken[3]); |
| 2034 | + int iStatus; |
| 2026 | 2035 | if( uvStatus==0 ) uvStatus = 2; |
| 2036 | + iStatus = unversioned_status(zName, mtime, zHash); |
| 2037 | + if( iStatus<=1 ){ |
| 2038 | + if( zHash[0]!='-' ){ |
| 2039 | + @ uvgimme %s(zName) |
| 2040 | + }else if( iStatus==1 ){ |
| 2041 | + db_multi_exec( |
| 2042 | + "UPDATE unversioned" |
| 2043 | + " SET mtime=%lld, hash=NULL, sz=0, encoding=0, content=NULL" |
| 2044 | + " WHERE name=%Q", mtime, zName |
| 2045 | + ); |
| 2046 | + db_unset("uv-hash", 0); |
| 2047 | + } |
| 2048 | + }else if( iStatus==2 ){ |
| 2049 | + db_multi_exec( |
| 2050 | + "UPDATE unversioned SET mtime=%lld WHERE name=%Q", mtime, zName |
| 2051 | + ); |
| 2052 | + db_unset("uv-hash", 0); |
| 2053 | + } |
| 2054 | + if( iStatus<=3 ){ |
| 2055 | + db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName); |
| 2056 | + }else if( iStatus==4 ){ |
| 2057 | + db_multi_exec("UPDATE uv_tosend SET mtimeOnly=1 WHERE name=%Q", zName); |
| 2058 | + } |
| 2027 | 2059 | }else |
| 2028 | 2060 | |
| 2029 | 2061 | /* push SERVERCODE PRODUCTCODE |
| 2030 | 2062 | ** |
| 2031 | 2063 | ** Should only happen in response to a clone. This message tells |
| | @@ -2136,10 +2168,11 @@ |
| 2136 | 2168 | */ |
| 2137 | 2169 | if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){ |
| 2138 | 2170 | uvStatus = 1; |
| 2139 | 2171 | }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){ |
| 2140 | 2172 | uvStatus = 2; |
| 2173 | + uvDoPush = 1; |
| 2141 | 2174 | } |
| 2142 | 2175 | }else |
| 2143 | 2176 | |
| 2144 | 2177 | /* error MESSAGE |
| 2145 | 2178 | ** |
| 2146 | 2179 | |