| | @@ -72,11 +72,17 @@ |
| 72 | 72 | /* |
| 73 | 73 | ** Remember that the other side of the connection already has a copy |
| 74 | 74 | ** of the file rid. |
| 75 | 75 | */ |
| 76 | 76 | static void remote_has(int rid){ |
| 77 | | - if( rid ) db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid); |
| 77 | + if( rid ){ |
| 78 | + static Stmt q; |
| 79 | + db_static_prepare(&q, "INSERT OR IGNORE INTO onremote VALUES(:r)"); |
| 80 | + db_bind_int(&q, ":r", rid); |
| 81 | + db_step(&q); |
| 82 | + db_reset(&q); |
| 83 | + } |
| 78 | 84 | } |
| 79 | 85 | |
| 80 | 86 | /* |
| 81 | 87 | ** The aToken[0..nToken-1] blob array is a parse of a "file" line |
| 82 | 88 | ** message. This routine finishes parsing that message and does |
| | @@ -95,11 +101,11 @@ |
| 95 | 101 | ** be initialized to an empty string. |
| 96 | 102 | ** |
| 97 | 103 | ** Any artifact successfully received by this routine is considered to |
| 98 | 104 | ** be public and is therefore removed from the "private" table. |
| 99 | 105 | */ |
| 100 | | -static void xfer_accept_file(Xfer *pXfer){ |
| 106 | +static void xfer_accept_file(Xfer *pXfer, int cloneFlag){ |
| 101 | 107 | int n; |
| 102 | 108 | int rid; |
| 103 | 109 | int srcid = 0; |
| 104 | 110 | Blob content, hash; |
| 105 | 111 | |
| | @@ -114,13 +120,26 @@ |
| 114 | 120 | return; |
| 115 | 121 | } |
| 116 | 122 | blob_zero(&content); |
| 117 | 123 | blob_zero(&hash); |
| 118 | 124 | blob_extract(pXfer->pIn, n, &content); |
| 119 | | - if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){ |
| 125 | + if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){ |
| 120 | 126 | /* Ignore files that have been shunned */ |
| 121 | 127 | return; |
| 128 | + } |
| 129 | + if( cloneFlag ){ |
| 130 | + if( pXfer->nToken==4 ){ |
| 131 | + srcid = rid_from_uuid(&pXfer->aToken[2], 1); |
| 132 | + pXfer->nDeltaRcvd++; |
| 133 | + }else{ |
| 134 | + srcid = 0; |
| 135 | + pXfer->nFileRcvd++; |
| 136 | + } |
| 137 | + rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid); |
| 138 | + remote_has(rid); |
| 139 | + blob_reset(&content); |
| 140 | + return; |
| 122 | 141 | } |
| 123 | 142 | if( pXfer->nToken==4 ){ |
| 124 | 143 | Blob src, next; |
| 125 | 144 | srcid = rid_from_uuid(&pXfer->aToken[2], 1); |
| 126 | 145 | if( content_get(srcid, &src)==0 ){ |
| | @@ -467,14 +486,15 @@ |
| 467 | 486 | ** Check to see if the number of unclustered entries is greater than |
| 468 | 487 | ** 100 and if it is, form a new cluster. Unclustered phantoms do not |
| 469 | 488 | ** count toward the 100 total. And phantoms are never added to a new |
| 470 | 489 | ** cluster. |
| 471 | 490 | */ |
| 472 | | -static void create_cluster(void){ |
| 491 | +void create_cluster(void){ |
| 473 | 492 | Blob cluster, cksum; |
| 474 | 493 | Stmt q; |
| 475 | 494 | int nUncl; |
| 495 | + int nRow = 0; |
| 476 | 496 | |
| 477 | 497 | /* We should not ever get any private artifacts in the unclustered table. |
| 478 | 498 | ** But if we do (because of a bug) now is a good time to delete them. */ |
| 479 | 499 | db_multi_exec( |
| 480 | 500 | "DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)" |
| | @@ -481,30 +501,41 @@ |
| 481 | 501 | ); |
| 482 | 502 | |
| 483 | 503 | nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/" |
| 484 | 504 | " WHERE NOT EXISTS(SELECT 1 FROM phantom" |
| 485 | 505 | " WHERE rid=unclustered.rid)"); |
| 486 | | - if( nUncl<100 ){ |
| 487 | | - return; |
| 488 | | - } |
| 489 | | - blob_zero(&cluster); |
| 490 | | - db_prepare(&q, "SELECT uuid FROM unclustered, blob" |
| 491 | | - " WHERE NOT EXISTS(SELECT 1 FROM phantom" |
| 492 | | - " WHERE rid=unclustered.rid)" |
| 493 | | - " AND unclustered.rid=blob.rid" |
| 494 | | - " AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 495 | | - " ORDER BY 1"); |
| 496 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 497 | | - blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0)); |
| 498 | | - } |
| 499 | | - db_finalize(&q); |
| 500 | | - md5sum_blob(&cluster, &cksum); |
| 501 | | - blob_appendf(&cluster, "Z %b\n", &cksum); |
| 502 | | - blob_reset(&cksum); |
| 503 | | - db_multi_exec("DELETE FROM unclustered"); |
| 504 | | - content_put(&cluster, 0, 0); |
| 505 | | - blob_reset(&cluster); |
| 506 | + if( nUncl>=100 ){ |
| 507 | + blob_zero(&cluster); |
| 508 | + db_prepare(&q, "SELECT uuid FROM unclustered, blob" |
| 509 | + " WHERE NOT EXISTS(SELECT 1 FROM phantom" |
| 510 | + " WHERE rid=unclustered.rid)" |
| 511 | + " AND unclustered.rid=blob.rid" |
| 512 | + " AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 513 | + " ORDER BY 1"); |
| 514 | + while( db_step(&q)==SQLITE_ROW ){ |
| 515 | + blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0)); |
| 516 | + nRow++; |
| 517 | + if( nRow>=800 && nUncl>nRow+100 ){ |
| 518 | + md5sum_blob(&cluster, &cksum); |
| 519 | + blob_appendf(&cluster, "Z %b\n", &cksum); |
| 520 | + blob_reset(&cksum); |
| 521 | + content_put(&cluster, 0, 0); |
| 522 | + blob_reset(&cluster); |
| 523 | + nUncl -= nRow; |
| 524 | + nRow = 0; |
| 525 | + } |
| 526 | + } |
| 527 | + db_finalize(&q); |
| 528 | + db_multi_exec("DELETE FROM unclustered"); |
| 529 | + if( nRow>0 ){ |
| 530 | + md5sum_blob(&cluster, &cksum); |
| 531 | + blob_appendf(&cluster, "Z %b\n", &cksum); |
| 532 | + blob_reset(&cksum); |
| 533 | + content_put(&cluster, 0, 0); |
| 534 | + blob_reset(&cluster); |
| 535 | + } |
| 536 | + } |
| 506 | 537 | } |
| 507 | 538 | |
| 508 | 539 | /* |
| 509 | 540 | ** Send an igot message for every entry in unclustered table. |
| 510 | 541 | ** Return the number of cards sent. |
| | @@ -606,19 +637,17 @@ |
| 606 | 637 | blobarray_zero(xfer.aToken, count(xfer.aToken)); |
| 607 | 638 | cgi_set_content_type(g.zContentType); |
| 608 | 639 | blob_zero(&xfer.err); |
| 609 | 640 | xfer.pIn = &g.cgiIn; |
| 610 | 641 | xfer.pOut = cgi_output_blob(); |
| 611 | | - xfer.mxSend = db_get_int("max-download", 5000000); |
| 642 | + xfer.mxSend = db_get_int("max-download", 20000000); |
| 612 | 643 | g.xferPanic = 1; |
| 613 | 644 | |
| 614 | 645 | db_begin_transaction(); |
| 615 | 646 | db_multi_exec( |
| 616 | 647 | "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" |
| 617 | 648 | ); |
| 618 | | - zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); |
| 619 | | - @ # timestamp %s(zNow) |
| 620 | 649 | manifest_crosslink_begin(); |
| 621 | 650 | while( blob_line(xfer.pIn, &xfer.line) ){ |
| 622 | 651 | if( blob_buffer(&xfer.line)[0]=='#' ) continue; |
| 623 | 652 | xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken)); |
| 624 | 653 | |
| | @@ -632,11 +661,11 @@ |
| 632 | 661 | cgi_reset_content(); |
| 633 | 662 | @ error not\sauthorized\sto\swrite |
| 634 | 663 | nErr++; |
| 635 | 664 | break; |
| 636 | 665 | } |
| 637 | | - xfer_accept_file(&xfer); |
| 666 | + xfer_accept_file(&xfer, 0); |
| 638 | 667 | if( blob_size(&xfer.err) ){ |
| 639 | 668 | cgi_reset_content(); |
| 640 | 669 | @ error %T(blob_str(&xfer.err)) |
| 641 | 670 | nErr++; |
| 642 | 671 | break; |
| | @@ -718,26 +747,42 @@ |
| 718 | 747 | isPush = 1; |
| 719 | 748 | } |
| 720 | 749 | } |
| 721 | 750 | }else |
| 722 | 751 | |
| 723 | | - /* clone |
| 752 | + /* clone ?PROTOCOL-VERSION? ?SEQUENCE-NUMBER? |
| 724 | 753 | ** |
| 725 | 754 | ** The client knows nothing. Tell all. |
| 726 | 755 | */ |
| 727 | 756 | if( blob_eq(&xfer.aToken[0], "clone") ){ |
| 757 | + int iVers; |
| 728 | 758 | login_check_credentials(); |
| 729 | 759 | if( !g.okClone ){ |
| 730 | 760 | cgi_reset_content(); |
| 731 | 761 | @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) |
| 732 | 762 | @ error not\sauthorized\sto\sclone |
| 733 | 763 | nErr++; |
| 734 | 764 | break; |
| 735 | 765 | } |
| 736 | | - isClone = 1; |
| 737 | | - isPull = 1; |
| 738 | | - deltaFlag = 1; |
| 766 | + if( xfer.nToken==3 |
| 767 | + && blob_is_int(&xfer.aToken[1], &iVers) |
| 768 | + && iVers>=2 |
| 769 | + ){ |
| 770 | + int seqno, max; |
| 771 | + blob_is_int(&xfer.aToken[2], &seqno); |
| 772 | + max = db_int(0, "SELECT max(rid) FROM blob"); |
| 773 | + while( xfer.mxSend>blob_size(xfer.pOut) && seqno<=max ){ |
| 774 | + send_file(&xfer, seqno, 0, 1); |
| 775 | + seqno++; |
| 776 | + } |
| 777 | + if( seqno>=max ) seqno = 0; |
| 778 | + @ clone_seqno %d(seqno) |
| 779 | + }else{ |
| 780 | + isClone = 1; |
| 781 | + isPull = 1; |
| 782 | + deltaFlag = 1; |
| 783 | + } |
| 739 | 784 | @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) |
| 740 | 785 | }else |
| 741 | 786 | |
| 742 | 787 | /* login USER NONCE SIGNATURE |
| 743 | 788 | ** |
| | @@ -874,10 +919,18 @@ |
| 874 | 919 | } |
| 875 | 920 | if( recvConfig ){ |
| 876 | 921 | configure_finalize_receive(); |
| 877 | 922 | } |
| 878 | 923 | manifest_crosslink_end(); |
| 924 | + |
| 925 | + /* Send the server timestamp last, in case prior processing happened |
| 926 | + ** to use up a significant fraction of our time window. |
| 927 | + */ |
| 928 | + zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); |
| 929 | + @ # timestamp %s(zNow) |
| 930 | + free(zNow); |
| 931 | + |
| 879 | 932 | db_end_transaction(0); |
| 880 | 933 | } |
| 881 | 934 | |
| 882 | 935 | /* |
| 883 | 936 | ** COMMAND: test-xfer |
| | @@ -943,13 +996,17 @@ |
| 943 | 996 | int origConfigRcvMask; /* Original value of configRcvMask */ |
| 944 | 997 | int nFileRecv; /* Number of files received */ |
| 945 | 998 | int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ |
| 946 | 999 | const char *zCookie; /* Server cookie */ |
| 947 | 1000 | int nSent, nRcvd; /* Bytes sent and received (after compression) */ |
| 1001 | + int cloneSeqno = 1; /* Sequence number for clones */ |
| 948 | 1002 | Blob send; /* Text we are sending to the server */ |
| 949 | 1003 | Blob recv; /* Reply we got back from the server */ |
| 950 | 1004 | Xfer xfer; /* Transfer data */ |
| 1005 | + int pctDone; /* Percentage done with a message */ |
| 1006 | + int lastPctDone = -1; /* Last displayed pctDone */ |
| 1007 | + double rArrivalTime; /* Time at which a message arrived */ |
| 951 | 1008 | const char *zSCode = db_get("server-code", "x"); |
| 952 | 1009 | const char *zPCode = db_get("project-code", 0); |
| 953 | 1010 | |
| 954 | 1011 | if( db_get_boolean("dont-push", 0) ) pushFlag = 0; |
| 955 | 1012 | if( pushFlag + pullFlag + cloneFlag == 0 |
| | @@ -977,15 +1034,16 @@ |
| 977 | 1034 | |
| 978 | 1035 | /* |
| 979 | 1036 | ** Always begin with a clone, pull, or push message |
| 980 | 1037 | */ |
| 981 | 1038 | if( cloneFlag ){ |
| 982 | | - blob_appendf(&send, "clone\n"); |
| 1039 | + blob_appendf(&send, "clone 2 %d\n", cloneSeqno); |
| 983 | 1040 | pushFlag = 0; |
| 984 | 1041 | pullFlag = 0; |
| 985 | 1042 | nCardSent++; |
| 986 | 1043 | /* TBD: Request all transferable configuration values */ |
| 1044 | + content_enable_dephantomize(0); |
| 987 | 1045 | }else if( pullFlag ){ |
| 988 | 1046 | blob_appendf(&send, "pull %s %s\n", zSCode, zPCode); |
| 989 | 1047 | nCardSent++; |
| 990 | 1048 | } |
| 991 | 1049 | if( pushFlag ){ |
| | @@ -1009,11 +1067,11 @@ |
| 1009 | 1067 | } |
| 1010 | 1068 | |
| 1011 | 1069 | /* Generate gimme cards for phantoms and leaf cards |
| 1012 | 1070 | ** for all leaves. |
| 1013 | 1071 | */ |
| 1014 | | - if( pullFlag || cloneFlag ){ |
| 1072 | + if( pullFlag || (cloneFlag && cloneSeqno==1) ){ |
| 1015 | 1073 | request_phantoms(&xfer, mxPhantomReq); |
| 1016 | 1074 | } |
| 1017 | 1075 | if( pushFlag ){ |
| 1018 | 1076 | send_unsent(&xfer); |
| 1019 | 1077 | nCardSent += send_unclustered(&xfer); |
| | @@ -1058,22 +1116,27 @@ |
| 1058 | 1116 | blob_appendf(&send, "# %s\n", zRandomness); |
| 1059 | 1117 | free(zRandomness); |
| 1060 | 1118 | |
| 1061 | 1119 | /* Exchange messages with the server */ |
| 1062 | 1120 | nFileSend = xfer.nFileSent + xfer.nDeltaSent; |
| 1063 | | - fossil_print(zValueFormat, "Send:", |
| 1121 | + fossil_print(zValueFormat, "Sent:", |
| 1064 | 1122 | blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent, |
| 1065 | 1123 | xfer.nFileSent, xfer.nDeltaSent); |
| 1066 | 1124 | nCardSent = 0; |
| 1067 | 1125 | nCardRcvd = 0; |
| 1068 | 1126 | xfer.nFileSent = 0; |
| 1069 | 1127 | xfer.nDeltaSent = 0; |
| 1070 | 1128 | xfer.nGimmeSent = 0; |
| 1071 | 1129 | xfer.nIGotSent = 0; |
| 1130 | + if( !g.cgiOutput && !g.fQuiet ){ |
| 1131 | + printf("waiting for server..."); |
| 1132 | + } |
| 1072 | 1133 | fflush(stdout); |
| 1073 | 1134 | http_exchange(&send, &recv, cloneFlag==0 || nCycle>0); |
| 1135 | + lastPctDone = -1; |
| 1074 | 1136 | blob_reset(&send); |
| 1137 | + rArrivalTime = db_double(0.0, "SELECT julianday('now')"); |
| 1075 | 1138 | |
| 1076 | 1139 | /* Begin constructing the next message (which might never be |
| 1077 | 1140 | ** sent) by beginning with the pull or push cards |
| 1078 | 1141 | */ |
| 1079 | 1142 | if( pullFlag ){ |
| | @@ -1086,18 +1149,21 @@ |
| 1086 | 1149 | } |
| 1087 | 1150 | go = 0; |
| 1088 | 1151 | |
| 1089 | 1152 | /* Process the reply that came back from the server */ |
| 1090 | 1153 | while( blob_line(&recv, &xfer.line) ){ |
| 1154 | + if( g.fHttpTrace ){ |
| 1155 | + printf("\rGOT: %.*s", blob_size(&xfer.line), blob_buffer(&xfer.line)); |
| 1156 | + } |
| 1091 | 1157 | if( blob_buffer(&xfer.line)[0]=='#' ){ |
| 1092 | 1158 | const char *zLine = blob_buffer(&xfer.line); |
| 1093 | 1159 | if( memcmp(zLine, "# timestamp ", 12)==0 ){ |
| 1094 | 1160 | char zTime[20]; |
| 1095 | 1161 | double rDiff; |
| 1096 | 1162 | sqlite3_snprintf(sizeof(zTime), zTime, "%.19s", &zLine[12]); |
| 1097 | | - rDiff = db_double(9e99, "SELECT julianday('%q') - julianday('now')", |
| 1098 | | - zTime); |
| 1163 | + rDiff = db_double(9e99, "SELECT julianday('%q') - %.17g", |
| 1164 | + zTime, rArrivalTime); |
| 1099 | 1165 | if( rDiff<0.0 ) rDiff = -rDiff; |
| 1100 | 1166 | if( rDiff>9e98 ) rDiff = 0.0; |
| 1101 | 1167 | if( (rDiff*24.0*3600.0)>=60.0 ){ |
| 1102 | 1168 | fossil_warning("*** time skew *** server time differs by %s", |
| 1103 | 1169 | db_timespan_name(rDiff)); |
| | @@ -1107,21 +1173,25 @@ |
| 1107 | 1173 | continue; |
| 1108 | 1174 | } |
| 1109 | 1175 | xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken)); |
| 1110 | 1176 | nCardRcvd++; |
| 1111 | 1177 | if( !g.cgiOutput && !g.fQuiet ){ |
| 1112 | | - printf("\r%d", nCardRcvd); |
| 1113 | | - fflush(stdout); |
| 1178 | + pctDone = (recv.iCursor*100)/recv.nUsed; |
| 1179 | + if( pctDone!=lastPctDone ){ |
| 1180 | + printf("\rprocessed: %d%% ", pctDone); |
| 1181 | + lastPctDone = pctDone; |
| 1182 | + fflush(stdout); |
| 1183 | + } |
| 1114 | 1184 | } |
| 1115 | 1185 | |
| 1116 | 1186 | /* file UUID SIZE \n CONTENT |
| 1117 | 1187 | ** file UUID DELTASRC SIZE \n CONTENT |
| 1118 | 1188 | ** |
| 1119 | 1189 | ** Receive a file transmitted from the server. |
| 1120 | 1190 | */ |
| 1121 | 1191 | if( blob_eq(&xfer.aToken[0],"file") ){ |
| 1122 | | - xfer_accept_file(&xfer); |
| 1192 | + xfer_accept_file(&xfer, cloneFlag); |
| 1123 | 1193 | }else |
| 1124 | 1194 | |
| 1125 | 1195 | /* gimme UUID |
| 1126 | 1196 | ** |
| 1127 | 1197 | ** Server is requesting a file. If the file is a manifest, assume |
| | @@ -1177,11 +1247,11 @@ |
| 1177 | 1247 | } |
| 1178 | 1248 | if( zPCode==0 ){ |
| 1179 | 1249 | zPCode = mprintf("%b", &xfer.aToken[2]); |
| 1180 | 1250 | db_set("project-code", zPCode, 0); |
| 1181 | 1251 | } |
| 1182 | | - blob_appendf(&send, "clone\n"); |
| 1252 | + blob_appendf(&send, "clone 2 %d\n", cloneSeqno); |
| 1183 | 1253 | nCardSent++; |
| 1184 | 1254 | }else |
| 1185 | 1255 | |
| 1186 | 1256 | /* config NAME SIZE \n CONTENT |
| 1187 | 1257 | ** |
| | @@ -1236,10 +1306,21 @@ |
| 1236 | 1306 | ** same server. |
| 1237 | 1307 | */ |
| 1238 | 1308 | if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){ |
| 1239 | 1309 | db_set("cookie", blob_str(&xfer.aToken[1]), 0); |
| 1240 | 1310 | }else |
| 1311 | + |
| 1312 | + /* clone_seqno N |
| 1313 | + ** |
| 1314 | + ** When doing a clone, the server tries to send all of its artifacts |
| 1315 | + ** in sequence. This card indicates the sequence number of the next |
| 1316 | + ** blob that needs to be sent. If N<=0 that indicates that all blobs |
| 1317 | + ** have been sent. |
| 1318 | + */ |
| 1319 | + if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){ |
| 1320 | + blob_is_int(&xfer.aToken[1], &cloneSeqno); |
| 1321 | + }else |
| 1241 | 1322 | |
| 1242 | 1323 | /* message MESSAGE |
| 1243 | 1324 | ** |
| 1244 | 1325 | ** Print a message. Similar to "error" but does not stop processing. |
| 1245 | 1326 | ** |
| | @@ -1314,10 +1395,12 @@ |
| 1314 | 1395 | nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile; |
| 1315 | 1396 | if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){ |
| 1316 | 1397 | go = 1; |
| 1317 | 1398 | mxPhantomReq = nFileRecv*2; |
| 1318 | 1399 | if( mxPhantomReq<200 ) mxPhantomReq = 200; |
| 1400 | + }else if( cloneFlag && nFileRecv>0 ){ |
| 1401 | + go = 1; |
| 1319 | 1402 | } |
| 1320 | 1403 | nCardRcvd = 0; |
| 1321 | 1404 | xfer.nFileRcvd = 0; |
| 1322 | 1405 | xfer.nDeltaRcvd = 0; |
| 1323 | 1406 | xfer.nDanglingFile = 0; |
| | @@ -1329,15 +1412,19 @@ |
| 1329 | 1412 | go = 1; |
| 1330 | 1413 | } |
| 1331 | 1414 | |
| 1332 | 1415 | /* If this is a clone, the go at least two rounds */ |
| 1333 | 1416 | if( cloneFlag && nCycle==1 ) go = 1; |
| 1417 | + |
| 1418 | + /* Stop the cycle if the server sends a "clone_seqno 0" card */ |
| 1419 | + if( cloneSeqno<=0 ) go = 0; |
| 1334 | 1420 | }; |
| 1335 | 1421 | transport_stats(&nSent, &nRcvd, 1); |
| 1336 | 1422 | fossil_print("Total network traffic: %d bytes sent, %d bytes received\n", |
| 1337 | 1423 | nSent, nRcvd); |
| 1338 | 1424 | transport_close(); |
| 1339 | 1425 | transport_global_shutdown(); |
| 1340 | 1426 | db_multi_exec("DROP TABLE onremote"); |
| 1341 | 1427 | manifest_crosslink_end(); |
| 1428 | + content_enable_dephantomize(1); |
| 1342 | 1429 | db_end_transaction(0); |
| 1343 | 1430 | } |
| 1344 | 1431 | |