Fossil SCM

Change the schema for the unversioned table. Add some initial code to do unversioned sync, but the code is incomplete and untested.

drh 2016-08-09 12:37 unversioned-files
Commit 73932a32c5564e66d883359084dbc0a1b03a05c8
+28
--- src/blob.c
+++ src/blob.c
@@ -653,19 +653,47 @@
653653
*/
654654
int blob_is_uuid(Blob *pBlob){
655655
return blob_size(pBlob)==UUID_SIZE
656656
&& validate16(blob_buffer(pBlob), UUID_SIZE);
657657
}
658
+
659
+/*
660
+** Return true if the blob contains a valid filename
661
+*/
662
+int blob_is_filename(Blob *pBlob){
663
+ return file_is_simple_pathname(blob_str(pBlob), 1);
664
+}
658665
659666
/*
660667
** Return true if the blob contains a valid 32-bit integer. Store
661668
** the integer value in *pValue.
662669
*/
663670
int blob_is_int(Blob *pBlob, int *pValue){
664671
const char *z = blob_buffer(pBlob);
665672
int i, n, c, v;
666673
n = blob_size(pBlob);
674
+ v = 0;
675
+ for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){
676
+ v = v*10 + c - '0';
677
+ }
678
+ if( i==n ){
679
+ *pValue = v;
680
+ return 1;
681
+ }else{
682
+ return 0;
683
+ }
684
+}
685
+
686
+/*
687
+** Return true if the blob contains a valid 64-bit integer. Store
688
+** the integer value in *pValue.
689
+*/
690
+int blob_is_int64(Blob *pBlob, sqlite3_int64 *pValue){
691
+ const char *z = blob_buffer(pBlob);
692
+ int i, n, c;
693
+ sqlite3_int64 v;
694
+ n = blob_size(pBlob);
667695
v = 0;
668696
for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){
669697
v = v*10 + c - '0';
670698
}
671699
if( i==n ){
672700
--- src/blob.c
+++ src/blob.c
@@ -653,19 +653,47 @@
653 */
654 int blob_is_uuid(Blob *pBlob){
655 return blob_size(pBlob)==UUID_SIZE
656 && validate16(blob_buffer(pBlob), UUID_SIZE);
657 }
 
 
 
 
 
 
 
658
659 /*
660 ** Return true if the blob contains a valid 32-bit integer. Store
661 ** the integer value in *pValue.
662 */
663 int blob_is_int(Blob *pBlob, int *pValue){
664 const char *z = blob_buffer(pBlob);
665 int i, n, c, v;
666 n = blob_size(pBlob);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667 v = 0;
668 for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){
669 v = v*10 + c - '0';
670 }
671 if( i==n ){
672
--- src/blob.c
+++ src/blob.c
@@ -653,19 +653,47 @@
653 */
654 int blob_is_uuid(Blob *pBlob){
655 return blob_size(pBlob)==UUID_SIZE
656 && validate16(blob_buffer(pBlob), UUID_SIZE);
657 }
658
659 /*
660 ** Return true if the blob contains a valid filename
661 */
662 int blob_is_filename(Blob *pBlob){
663 return file_is_simple_pathname(blob_str(pBlob), 1);
664 }
665
666 /*
667 ** Return true if the blob contains a valid 32-bit integer. Store
668 ** the integer value in *pValue.
669 */
670 int blob_is_int(Blob *pBlob, int *pValue){
671 const char *z = blob_buffer(pBlob);
672 int i, n, c, v;
673 n = blob_size(pBlob);
674 v = 0;
675 for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){
676 v = v*10 + c - '0';
677 }
678 if( i==n ){
679 *pValue = v;
680 return 1;
681 }else{
682 return 0;
683 }
684 }
685
686 /*
687 ** Return true if the blob contains a valid 64-bit integer. Store
688 ** the integer value in *pValue.
689 */
690 int blob_is_int64(Blob *pBlob, sqlite3_int64 *pValue){
691 const char *z = blob_buffer(pBlob);
692 int i, n, c;
693 sqlite3_int64 v;
694 n = blob_size(pBlob);
695 v = 0;
696 for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){
697 v = v*10 + c - '0';
698 }
699 if( i==n ){
700
+1 -6
--- src/doc.c
+++ src/doc.c
@@ -632,16 +632,11 @@
632632
}else{
633633
goto doc_not_found;
634634
}
635635
}
636636
if( isUV ){
637
- if( db_exists("SELECT 1 FROM unversioned"
638
- " WHERE name=%Q AND content IS NOT NULL", zName) ){
639
- blob_init(&filebody, 0, 0);
640
- db_blob(&filebody, "SELECT content FROM unversioned WHERE name=%Q",
641
- zName);
642
- blob_uncompress(&filebody, &filebody);
637
+ if( unversioned_content(zName, &filebody)==0 ){
643638
rid = 1;
644639
zDfltTitle = zName;
645640
}
646641
}else if( fossil_strcmp(zCheckin,"ckout")==0 ){
647642
/* Read from the local checkout */
648643
--- src/doc.c
+++ src/doc.c
@@ -632,16 +632,11 @@
632 }else{
633 goto doc_not_found;
634 }
635 }
636 if( isUV ){
637 if( db_exists("SELECT 1 FROM unversioned"
638 " WHERE name=%Q AND content IS NOT NULL", zName) ){
639 blob_init(&filebody, 0, 0);
640 db_blob(&filebody, "SELECT content FROM unversioned WHERE name=%Q",
641 zName);
642 blob_uncompress(&filebody, &filebody);
643 rid = 1;
644 zDfltTitle = zName;
645 }
646 }else if( fossil_strcmp(zCheckin,"ckout")==0 ){
647 /* Read from the local checkout */
648
--- src/doc.c
+++ src/doc.c
@@ -632,16 +632,11 @@
632 }else{
633 goto doc_not_found;
634 }
635 }
636 if( isUV ){
637 if( unversioned_content(zName, &filebody)==0 ){
 
 
 
 
 
638 rid = 1;
639 zDfltTitle = zName;
640 }
641 }else if( fossil_strcmp(zCheckin,"ckout")==0 ){
642 /* Read from the local checkout */
643
+3
--- src/sync.c
+++ src/sync.c
@@ -137,10 +137,13 @@
137137
** to be exchanged. This can overcome malfunctions in the sync protocol.
138138
*/
139139
if( find_option("verily",0,0)!=0 ){
140140
*pSyncFlags |= SYNC_RESYNC;
141141
}
142
+ if( find_option("uv",0,0)!=0 ){
143
+ *pSyncFlags |= SYNC_UNVERSIONED;
144
+ }
142145
url_proxy_options();
143146
clone_ssh_find_options();
144147
db_find_and_open_repository(0, 0);
145148
db_open_config(0, 0);
146149
if( g.argc==2 ){
147150
--- src/sync.c
+++ src/sync.c
@@ -137,10 +137,13 @@
137 ** to be exchanged. This can overcome malfunctions in the sync protocol.
138 */
139 if( find_option("verily",0,0)!=0 ){
140 *pSyncFlags |= SYNC_RESYNC;
141 }
 
 
 
142 url_proxy_options();
143 clone_ssh_find_options();
144 db_find_and_open_repository(0, 0);
145 db_open_config(0, 0);
146 if( g.argc==2 ){
147
--- src/sync.c
+++ src/sync.c
@@ -137,10 +137,13 @@
137 ** to be exchanged. This can overcome malfunctions in the sync protocol.
138 */
139 if( find_option("verily",0,0)!=0 ){
140 *pSyncFlags |= SYNC_RESYNC;
141 }
142 if( find_option("uv",0,0)!=0 ){
143 *pSyncFlags |= SYNC_UNVERSIONED;
144 }
145 url_proxy_options();
146 clone_ssh_find_options();
147 db_find_and_open_repository(0, 0);
148 db_open_config(0, 0);
149 if( g.argc==2 ){
150
+36 -111
--- src/unversioned.c
+++ src/unversioned.c
@@ -34,13 +34,14 @@
3434
static const char zUnversionedInit[] =
3535
@ CREATE TABLE IF NOT EXISTS "%w".unversioned(
3636
@ name TEXT PRIMARY KEY, -- Name of the uv file
3737
@ rcvid INTEGER, -- Where received from
3838
@ mtime DATETIME, -- timestamp. Seconds since 1970.
39
-@ hash TEXT, -- Content hash
39
+@ hash TEXT, -- Content hash. NULL if a delete marker
4040
@ sz INTEGER, -- size of content after decompression
41
-@ content BLOB -- zlib compressed content
41
+@ encoding INT, -- 0: plaintext. 1: zlib compressed
42
+@ content BLOB -- content of the file. NULL if oversized
4243
@ ) WITHOUT ROWID;
4344
;
4445
4546
/*
4647
** Make sure the unversioned table exists in the repository.
@@ -79,10 +80,31 @@
7980
db_set("uv-hash", sha1sum_finish(0), 0);
8081
zHash = db_get("uv-hash",0);
8182
}
8283
return zHash;
8384
}
85
+
86
+/*
87
+** Initialize pContent to be the content of an unversioned file zName.
88
+**
89
+** Return 0 on success. Return 1 if zName is not found.
90
+*/
91
+int unversioned_content(const char *zName, Blob *pContent){
92
+ Stmt q;
93
+ int rc = 1;
94
+ blob_init(pContent, 0, 0);
95
+ db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE name=%Q", zName);
96
+ if( db_step(&q)==SQLITE_ROW ){
97
+ db_column_blob(&q, 1, pContent);
98
+ if( db_column_int(&q, 0)==1 ){
99
+ blob_uncompress(pContent, pContent);
100
+ }
101
+ rc = 0;
102
+ }
103
+ db_finalize(&q);
104
+ return rc;
105
+}
84106
85107
/*
86108
** COMMAND: unversioned
87109
**
88110
** Usage: %fossil unversioned SUBCOMMAND ARGS...
@@ -151,12 +173,12 @@
151173
if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
152174
verify_all_options();
153175
db_begin_transaction();
154176
content_rcvid_init();
155177
db_prepare(&ins,
156
- "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,content)"
157
- " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
178
+ "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)"
179
+ " VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)"
158180
);
159181
for(i=3; i<g.argc; i++){
160182
zIn = zAs ? zAs : g.argv[i];
161183
if( zIn[0]==0 || zIn[0]=='/' || !file_is_simple_pathname(zIn,1) ){
162184
fossil_fatal("'%Q' is not an acceptable filename", zIn);
@@ -168,11 +190,17 @@
168190
db_bind_text(&ins, ":name", zIn);
169191
db_bind_int(&ins, ":rcvid", g.rcvid);
170192
db_bind_int64(&ins, ":mtime", mtime);
171193
db_bind_text(&ins, ":hash", blob_str(&hash));
172194
db_bind_int(&ins, ":sz", blob_size(&file));
173
- db_bind_blob(&ins, ":content", &compressed);
195
+ if( blob_size(&compressed) <= 0.8*blob_size(&file) ){
196
+ db_bind_int(&ins, ":encoding", 1);
197
+ db_bind_blob(&ins, ":content", &compressed);
198
+ }else{
199
+ db_bind_int(&ins, ":encoding", 0);
200
+ db_bind_blob(&ins, ":content", &file);
201
+ }
174202
db_step(&ins);
175203
db_reset(&ins);
176204
blob_reset(&compressed);
177205
blob_reset(&hash);
178206
blob_reset(&file);
@@ -184,30 +212,23 @@
184212
int i;
185213
verify_all_options();
186214
db_begin_transaction();
187215
for(i=3; i<g.argc; i++){
188216
Blob content;
189
- blob_init(&content, 0, 0);
190
- db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q",g.argv[i]);
191
- if( blob_size(&content)==0 ){
192
- fossil_fatal("no such uv-file: %Q", g.argv[i]);
217
+ if( unversioned_content(g.argv[i], &content)==0 ){
218
+ blob_write_to_file(&content, "-");
193219
}
194
- blob_uncompress(&content, &content);
195
- blob_write_to_file(&content, "-");
196220
blob_reset(&content);
197221
}
198222
db_end_transaction(0);
199223
}else if( memcmp(zCmd, "export", nCmd)==0 ){
200224
Blob content;
201225
verify_all_options();
202226
if( g.argc!=5 ) usage("export UVFILE OUTPUT");
203
- blob_init(&content, 0, 0);
204
- db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q", g.argv[3]);
205
- if( blob_size(&content)==0 ){
227
+ if( unversioned_content(g.argv[3], &content) ){
206228
fossil_fatal("no such uv-file: %Q", g.argv[3]);
207229
}
208
- blob_uncompress(&content, &content);
209230
blob_write_to_file(&content, g.argv[4]);
210231
blob_reset(&content);
211232
}else if( memcmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
212233
/* Show the hash value used during uv sync */
213234
int debugFlag = find_option("debug",0,0)!=0;
@@ -269,101 +290,5 @@
269290
db_end_transaction(0);
270291
}else{
271292
usage("add|cat|export|ls|revert|rm|sync");
272293
}
273294
}
274
-
275
-#if 0
276
-***************************************************************************
277
-DESIGN NOTES
278
-Web interface:
279
-
280
- /uv/NAME
281
- /uvctrl
282
- /uvctrl?add=NAME&content=CONTENT
283
- /uvctrl?rm=NAME
284
-
285
-Sync protocol:
286
-
287
-Client sends
288
-
289
- pragma uvhash UVHASH
290
-
291
-If the server support UV and if the UVHASH is different, then the
292
-server replies with one or more of:
293
-
294
- uvigot NAME TIMESTAMP HASH
295
-
296
-The HASH argument is omitted from deleted uv files. The UVHASH in
297
-the initial pragma is simply the SHA1 of the uvigot lines, each
298
-terminated by \n, in lexicographical order.
299
-
300
-The client examines the uvigot lines and for each difference
301
-issues either:
302
-
303
- uvgimme NAME
304
-
305
-or
306
-
307
- uvfile NAME TIMESTAMP SIZE FLAGS \n CONTENT
308
-
309
-The client sends uvgimme if
310
-
311
- (a) it does not possess NAME or
312
- (b) if the NAME it holds has an earlier timestamp than TIMESTAMP or
313
- (c) if the NAME it holds has the exact timestamp TIMESTAMP but a
314
- lexicographically earliers HASH.
315
-
316
-Otherwise the client sends a uvfile. The client also sends uvfile
317
-cards for each unversioned file it holds which was not named by any
318
-uvigot card.
319
-
320
-On the uvfile card, the FLAGS value is an unsigned integer with
321
-the meaning assigned to the following bits:
322
-
323
- 0x0001 Record is deleted. No CONTENT transmitted.
324
-
325
- 0x0002 CONTENT is zlib compressed. SIZE is the compressed size.
326
-
327
- 0x0004 CONTENT is oversize and is omitted. HASH sent instead. SIZE
328
- is the uncompressed size
329
-
330
-New FLAGS values may be added in future releases.
331
-
332
-Internal representation:
333
-
334
- CREATE TABLE unversioned(
335
- name TEXT PRIMARY KEY, -- Name of the uv file
336
- rcvid INTEGER, -- Where received from
337
- mtime DATETIME, -- timestamp. Julian day
338
- hash TEXT, -- Content hash
339
- sz INTEGER, -- size of content after decompression
340
- content BLOB -- zlib compressed content
341
- ) WITHOUT ROWID;
342
-
343
-The hash field is NULL for deleted content. The content field is
344
-NULL for content that is unavailable.
345
-
346
-Other notes:
347
-
348
-The mimetype of UV content is determine by the suffix on the
349
-filename.
350
-
351
-UV content can be sent to any user with read/check-out privilege 'o'.
352
-New UV content will be accepted from users with write/check-in privilege 'i'.
353
-
354
-The current UVHASH value can be cached in the config table under the
355
-name of "uvhash".
356
-
357
-Clients that are UV-aware and would like to be initialized with UV
358
-content may send "pragma uvhash 0" upon initial clone, and the server
359
-will include all necessary uvfile cards in its replies.
360
-
361
-Clients or servers may send "pragma uv-size-limit SIZE" to inform the
362
-other side that UV files larger than SIZE should be transmitted using
363
-the "4" flag ("content omitted"). The hash is an extra parameter on
364
-the end of a uvfile/4 card.
365
-
366
-Clients and servers reject any UVFile with a timestamp that is too
367
-far in the future.
368
-***************************************************************************
369
-#endif
370295
--- src/unversioned.c
+++ src/unversioned.c
@@ -34,13 +34,14 @@
34 static const char zUnversionedInit[] =
35 @ CREATE TABLE IF NOT EXISTS "%w".unversioned(
36 @ name TEXT PRIMARY KEY, -- Name of the uv file
37 @ rcvid INTEGER, -- Where received from
38 @ mtime DATETIME, -- timestamp. Seconds since 1970.
39 @ hash TEXT, -- Content hash
40 @ sz INTEGER, -- size of content after decompression
41 @ content BLOB -- zlib compressed content
 
42 @ ) WITHOUT ROWID;
43 ;
44
45 /*
46 ** Make sure the unversioned table exists in the repository.
@@ -79,10 +80,31 @@
79 db_set("uv-hash", sha1sum_finish(0), 0);
80 zHash = db_get("uv-hash",0);
81 }
82 return zHash;
83 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
85 /*
86 ** COMMAND: unversioned
87 **
88 ** Usage: %fossil unversioned SUBCOMMAND ARGS...
@@ -151,12 +173,12 @@
151 if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
152 verify_all_options();
153 db_begin_transaction();
154 content_rcvid_init();
155 db_prepare(&ins,
156 "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,content)"
157 " VALUES(:name,:rcvid,:mtime,:hash,:sz,:content)"
158 );
159 for(i=3; i<g.argc; i++){
160 zIn = zAs ? zAs : g.argv[i];
161 if( zIn[0]==0 || zIn[0]=='/' || !file_is_simple_pathname(zIn,1) ){
162 fossil_fatal("'%Q' is not an acceptable filename", zIn);
@@ -168,11 +190,17 @@
168 db_bind_text(&ins, ":name", zIn);
169 db_bind_int(&ins, ":rcvid", g.rcvid);
170 db_bind_int64(&ins, ":mtime", mtime);
171 db_bind_text(&ins, ":hash", blob_str(&hash));
172 db_bind_int(&ins, ":sz", blob_size(&file));
173 db_bind_blob(&ins, ":content", &compressed);
 
 
 
 
 
 
174 db_step(&ins);
175 db_reset(&ins);
176 blob_reset(&compressed);
177 blob_reset(&hash);
178 blob_reset(&file);
@@ -184,30 +212,23 @@
184 int i;
185 verify_all_options();
186 db_begin_transaction();
187 for(i=3; i<g.argc; i++){
188 Blob content;
189 blob_init(&content, 0, 0);
190 db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q",g.argv[i]);
191 if( blob_size(&content)==0 ){
192 fossil_fatal("no such uv-file: %Q", g.argv[i]);
193 }
194 blob_uncompress(&content, &content);
195 blob_write_to_file(&content, "-");
196 blob_reset(&content);
197 }
198 db_end_transaction(0);
199 }else if( memcmp(zCmd, "export", nCmd)==0 ){
200 Blob content;
201 verify_all_options();
202 if( g.argc!=5 ) usage("export UVFILE OUTPUT");
203 blob_init(&content, 0, 0);
204 db_blob(&content, "SELECT content FROM unversioned WHERE name=%Q", g.argv[3]);
205 if( blob_size(&content)==0 ){
206 fossil_fatal("no such uv-file: %Q", g.argv[3]);
207 }
208 blob_uncompress(&content, &content);
209 blob_write_to_file(&content, g.argv[4]);
210 blob_reset(&content);
211 }else if( memcmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
212 /* Show the hash value used during uv sync */
213 int debugFlag = find_option("debug",0,0)!=0;
@@ -269,101 +290,5 @@
269 db_end_transaction(0);
270 }else{
271 usage("add|cat|export|ls|revert|rm|sync");
272 }
273 }
274
275 #if 0
276 ***************************************************************************
277 DESIGN NOTES
278 Web interface:
279
280 /uv/NAME
281 /uvctrl
282 /uvctrl?add=NAME&content=CONTENT
283 /uvctrl?rm=NAME
284
285 Sync protocol:
286
287 Client sends
288
289 pragma uvhash UVHASH
290
291 If the server support UV and if the UVHASH is different, then the
292 server replies with one or more of:
293
294 uvigot NAME TIMESTAMP HASH
295
296 The HASH argument is omitted from deleted uv files. The UVHASH in
297 the initial pragma is simply the SHA1 of the uvigot lines, each
298 terminated by \n, in lexicographical order.
299
300 The client examines the uvigot lines and for each difference
301 issues either:
302
303 uvgimme NAME
304
305 or
306
307 uvfile NAME TIMESTAMP SIZE FLAGS \n CONTENT
308
309 The client sends uvgimme if
310
311 (a) it does not possess NAME or
312 (b) if the NAME it holds has an earlier timestamp than TIMESTAMP or
313 (c) if the NAME it holds has the exact timestamp TIMESTAMP but a
314 lexicographically earliers HASH.
315
316 Otherwise the client sends a uvfile. The client also sends uvfile
317 cards for each unversioned file it holds which was not named by any
318 uvigot card.
319
320 On the uvfile card, the FLAGS value is an unsigned integer with
321 the meaning assigned to the following bits:
322
323 0x0001 Record is deleted. No CONTENT transmitted.
324
325 0x0002 CONTENT is zlib compressed. SIZE is the compressed size.
326
327 0x0004 CONTENT is oversize and is omitted. HASH sent instead. SIZE
328 is the uncompressed size
329
330 New FLAGS values may be added in future releases.
331
332 Internal representation:
333
334 CREATE TABLE unversioned(
335 name TEXT PRIMARY KEY, -- Name of the uv file
336 rcvid INTEGER, -- Where received from
337 mtime DATETIME, -- timestamp. Julian day
338 hash TEXT, -- Content hash
339 sz INTEGER, -- size of content after decompression
340 content BLOB -- zlib compressed content
341 ) WITHOUT ROWID;
342
343 The hash field is NULL for deleted content. The content field is
344 NULL for content that is unavailable.
345
346 Other notes:
347
348 The mimetype of UV content is determine by the suffix on the
349 filename.
350
351 UV content can be sent to any user with read/check-out privilege 'o'.
352 New UV content will be accepted from users with write/check-in privilege 'i'.
353
354 The current UVHASH value can be cached in the config table under the
355 name of "uvhash".
356
357 Clients that are UV-aware and would like to be initialized with UV
358 content may send "pragma uvhash 0" upon initial clone, and the server
359 will include all necessary uvfile cards in its replies.
360
361 Clients or servers may send "pragma uv-size-limit SIZE" to inform the
362 other side that UV files larger than SIZE should be transmitted using
363 the "4" flag ("content omitted"). The hash is an extra parameter on
364 the end of a uvfile/4 card.
365
366 Clients and servers reject any UVFile with a timestamp that is too
367 far in the future.
368 ***************************************************************************
369 #endif
370
--- src/unversioned.c
+++ src/unversioned.c
@@ -34,13 +34,14 @@
34 static const char zUnversionedInit[] =
35 @ CREATE TABLE IF NOT EXISTS "%w".unversioned(
36 @ name TEXT PRIMARY KEY, -- Name of the uv file
37 @ rcvid INTEGER, -- Where received from
38 @ mtime DATETIME, -- timestamp. Seconds since 1970.
39 @ hash TEXT, -- Content hash. NULL if a delete marker
40 @ sz INTEGER, -- size of content after decompression
41 @ encoding INT, -- 0: plaintext. 1: zlib compressed
42 @ content BLOB -- content of the file. NULL if oversized
43 @ ) WITHOUT ROWID;
44 ;
45
46 /*
47 ** Make sure the unversioned table exists in the repository.
@@ -79,10 +80,31 @@
80 db_set("uv-hash", sha1sum_finish(0), 0);
81 zHash = db_get("uv-hash",0);
82 }
83 return zHash;
84 }
85
86 /*
87 ** Initialize pContent to be the content of an unversioned file zName.
88 **
89 ** Return 0 on success. Return 1 if zName is not found.
90 */
91 int unversioned_content(const char *zName, Blob *pContent){
92 Stmt q;
93 int rc = 1;
94 blob_init(pContent, 0, 0);
95 db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE name=%Q", zName);
96 if( db_step(&q)==SQLITE_ROW ){
97 db_column_blob(&q, 1, pContent);
98 if( db_column_int(&q, 0)==1 ){
99 blob_uncompress(pContent, pContent);
100 }
101 rc = 0;
102 }
103 db_finalize(&q);
104 return rc;
105 }
106
107 /*
108 ** COMMAND: unversioned
109 **
110 ** Usage: %fossil unversioned SUBCOMMAND ARGS...
@@ -151,12 +173,12 @@
173 if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
174 verify_all_options();
175 db_begin_transaction();
176 content_rcvid_init();
177 db_prepare(&ins,
178 "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)"
179 " VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)"
180 );
181 for(i=3; i<g.argc; i++){
182 zIn = zAs ? zAs : g.argv[i];
183 if( zIn[0]==0 || zIn[0]=='/' || !file_is_simple_pathname(zIn,1) ){
184 fossil_fatal("'%Q' is not an acceptable filename", zIn);
@@ -168,11 +190,17 @@
190 db_bind_text(&ins, ":name", zIn);
191 db_bind_int(&ins, ":rcvid", g.rcvid);
192 db_bind_int64(&ins, ":mtime", mtime);
193 db_bind_text(&ins, ":hash", blob_str(&hash));
194 db_bind_int(&ins, ":sz", blob_size(&file));
195 if( blob_size(&compressed) <= 0.8*blob_size(&file) ){
196 db_bind_int(&ins, ":encoding", 1);
197 db_bind_blob(&ins, ":content", &compressed);
198 }else{
199 db_bind_int(&ins, ":encoding", 0);
200 db_bind_blob(&ins, ":content", &file);
201 }
202 db_step(&ins);
203 db_reset(&ins);
204 blob_reset(&compressed);
205 blob_reset(&hash);
206 blob_reset(&file);
@@ -184,30 +212,23 @@
212 int i;
213 verify_all_options();
214 db_begin_transaction();
215 for(i=3; i<g.argc; i++){
216 Blob content;
217 if( unversioned_content(g.argv[i], &content)==0 ){
218 blob_write_to_file(&content, "-");
 
 
219 }
 
 
220 blob_reset(&content);
221 }
222 db_end_transaction(0);
223 }else if( memcmp(zCmd, "export", nCmd)==0 ){
224 Blob content;
225 verify_all_options();
226 if( g.argc!=5 ) usage("export UVFILE OUTPUT");
227 if( unversioned_content(g.argv[3], &content) ){
 
 
228 fossil_fatal("no such uv-file: %Q", g.argv[3]);
229 }
 
230 blob_write_to_file(&content, g.argv[4]);
231 blob_reset(&content);
232 }else if( memcmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
233 /* Show the hash value used during uv sync */
234 int debugFlag = find_option("debug",0,0)!=0;
@@ -269,101 +290,5 @@
290 db_end_transaction(0);
291 }else{
292 usage("add|cat|export|ls|revert|rm|sync");
293 }
294 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
+314 -10
--- src/xfer.c
+++ src/xfer.c
@@ -282,10 +282,122 @@
282282
Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]),
283283
blob_size(&pXfer->aToken[1]));
284284
remote_has(rid);
285285
blob_reset(&content);
286286
}
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
+}
287399
288400
/*
289401
** Try to send a file as a delta against its parent.
290402
** If successful, return the number of bytes in the delta.
291403
** If we cannot generate an appropriate delta, then send
@@ -524,10 +636,47 @@
524636
blob_reset(&fullContent);
525637
}
526638
}
527639
db_reset(&q1);
528640
}
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
+}
529678
530679
/*
531680
** Send a gimme message for every phantom.
532681
**
533682
** Except: do not request shunned artifacts. And do not request
@@ -833,10 +982,40 @@
833982
blob_size(&content), blob_str(&content));
834983
blob_reset(&content);
835984
}
836985
}
837986
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
+
8381017
/*
8391018
** Called when there is an attempt to transfer private content to and
8401019
** from a server without authorization.
8411020
*/
8421021
static void server_private_xfer_not_authorized(void){
@@ -1026,10 +1205,24 @@
10261205
@ error %T(blob_str(&xfer.err))
10271206
nErr++;
10281207
break;
10291208
}
10301209
}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
10311224
10321225
/* gimme UUID
10331226
**
10341227
** Client is requesting a file. Send it.
10351228
*/
@@ -1043,10 +1236,21 @@
10431236
if( rid ){
10441237
send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
10451238
}
10461239
}
10471240
}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
10481252
10491253
/* igot UUID ?ISPRIVATE?
10501254
**
10511255
** Client announces that it has a particular file. If the ISPRIVATE
10521256
** argument exists and is non-zero, then the file is a private file.
@@ -1269,10 +1473,11 @@
12691473
** The client issue pragmas to try to influence the behavior of the
12701474
** server. These are requests only. Unknown pragmas are silently
12711475
** ignored.
12721476
*/
12731477
if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
1478
+
12741479
/* pragma send-private
12751480
**
12761481
** If the user has the "x" privilege (which must be set explicitly -
12771482
** it is not automatic with "a" or "s") then this pragma causes
12781483
** private information to be pulled in addition to public records.
@@ -1283,17 +1488,34 @@
12831488
server_private_xfer_not_authorized();
12841489
}else{
12851490
xfer.syncPrivate = 1;
12861491
}
12871492
}
1493
+
12881494
/* pragma send-catalog
12891495
**
12901496
** Send igot cards for all known artifacts.
12911497
*/
12921498
if( blob_eq(&xfer.aToken[1], "send-catalog") ){
12931499
xfer.resync = 0x7fffffff;
12941500
}
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
+ }
12951517
}else
12961518
12971519
/* Unknown message
12981520
*/
12991521
{
@@ -1391,16 +1613,17 @@
13911613
13921614
#if INTERFACE
13931615
/*
13941616
** Flag options for controlling client_sync()
13951617
*/
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
14021625
#endif
14031626
14041627
/*
14051628
** Floating-point absolute value
14061629
*/
@@ -1423,11 +1646,11 @@
14231646
){
14241647
int go = 1; /* Loop until zero */
14251648
int nCardSent = 0; /* Number of cards sent */
14261649
int nCardRcvd = 0; /* Number of cards received */
14271650
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 */
14291652
int origConfigRcvMask; /* Original value of configRcvMask */
14301653
int nFileRecv; /* Number of files received */
14311654
int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
14321655
const char *zCookie; /* Server cookie */
14331656
i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
@@ -1444,13 +1667,17 @@
14441667
int nRoundtrip= 0; /* Number of HTTP requests */
14451668
int nArtifactSent = 0; /* Total artifacts sent */
14461669
int nArtifactRcvd = 0; /* Total artifacts received */
14471670
const char *zOpType = 0;/* Push, Pull, Sync, Clone */
14481671
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 */
14491676
14501677
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
14521679
&& configRcvMask==0 && configSendMask==0 ) return 0;
14531680
14541681
transport_stats(0, 0, 1);
14551682
socket_global_init();
14561683
memset(&xfer, 0, sizeof(xfer));
@@ -1473,10 +1700,17 @@
14731700
14741701
/* Send the send-private pragma if we are trying to sync private data */
14751702
if( syncFlags & SYNC_PRIVATE ){
14761703
blob_append(&send, "pragma send-private\n", -1);
14771704
}
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
+ }
14781712
14791713
/*
14801714
** Always begin with a clone, pull, or push message
14811715
*/
14821716
if( syncFlags & SYNC_CLONE ){
@@ -1514,11 +1748,11 @@
15141748
db_multi_exec(
15151749
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
15161750
);
15171751
manifest_crosslink_begin();
15181752
1519
- /* Send make the most recently received cookie. Let the server
1753
+ /* Send back the most recently received cookie. Let the server
15201754
** figure out if this is a cookie that it cares about.
15211755
*/
15221756
zCookie = db_get("cookie", 0);
15231757
if( zCookie ){
15241758
blob_appendf(&send, "cookie %s\n", zCookie);
@@ -1558,10 +1792,23 @@
15581792
configure_prepare_to_receive(overwrite);
15591793
}
15601794
origConfigRcvMask = configRcvMask;
15611795
configRcvMask = 0;
15621796
}
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
+ }
15631810
15641811
/* Send configuration parameters being pushed */
15651812
if( configSendMask ){
15661813
if( zOpType==0 ) zOpType = "Push";
15671814
if( configSendMask & CONFIGSET_OLDFORMAT ){
@@ -1575,10 +1822,30 @@
15751822
}else{
15761823
nCardSent += configure_send_group(xfer.pOut, configSendMask, 0);
15771824
}
15781825
configSendMask = 0;
15791826
}
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
+ }
15801847
15811848
/* Append randomness to the end of the message. This makes all
15821849
** messages unique so that that the login-card nonce will always
15831850
** be unique.
15841851
*/
@@ -1683,10 +1950,18 @@
16831950
*/
16841951
if( blob_eq(&xfer.aToken[0],"cfile") ){
16851952
xfer_accept_compressed_file(&xfer, 0, 0);
16861953
nArtifactRcvd++;
16871954
}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
16881963
16891964
/* gimme UUID
16901965
**
16911966
** Server is requesting a file. If the file is a manifest, assume
16921967
** that the server will also want to know all of the content files
@@ -1730,10 +2005,28 @@
17302005
if( rid ) newPhantom = 1;
17312006
}
17322007
remote_has(rid);
17332008
}else
17342009
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
17352028
17362029
/* push SERVERCODE PRODUCTCODE
17372030
**
17382031
** Should only happen in response to a clone. This message tells
17392032
** the client what product to use for the new database.
@@ -1833,10 +2126,21 @@
18332126
** The server can send pragmas to try to convey meta-information to
18342127
** the client. These are informational only. Unknown pragmas are
18352128
** silently ignored.
18362129
*/
18372130
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
+ }
18382142
}else
18392143
18402144
/* error MESSAGE
18412145
**
18422146
** Report an error and abandon the sync session.
@@ -1931,11 +2235,11 @@
19312235
xfer.nDanglingFile = 0;
19322236
19332237
/* If we have one or more files queued to send, then go
19342238
** another round
19352239
*/
1936
- if( xfer.nFileSent+xfer.nDeltaSent>0 ){
2240
+ if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
19372241
go = 1;
19382242
}
19392243
19402244
/* If this is a clone, the go at least two rounds */
19412245
if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
19422246
--- src/xfer.c
+++ src/xfer.c
@@ -282,10 +282,122 @@
282 Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]),
283 blob_size(&pXfer->aToken[1]));
284 remote_has(rid);
285 blob_reset(&content);
286 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
288 /*
289 ** Try to send a file as a delta against its parent.
290 ** If successful, return the number of bytes in the delta.
291 ** If we cannot generate an appropriate delta, then send
@@ -524,10 +636,47 @@
524 blob_reset(&fullContent);
525 }
526 }
527 db_reset(&q1);
528 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
530 /*
531 ** Send a gimme message for every phantom.
532 **
533 ** Except: do not request shunned artifacts. And do not request
@@ -833,10 +982,40 @@
833 blob_size(&content), blob_str(&content));
834 blob_reset(&content);
835 }
836 }
837
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
838 /*
839 ** Called when there is an attempt to transfer private content to and
840 ** from a server without authorization.
841 */
842 static void server_private_xfer_not_authorized(void){
@@ -1026,10 +1205,24 @@
1026 @ error %T(blob_str(&xfer.err))
1027 nErr++;
1028 break;
1029 }
1030 }else
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
1032 /* gimme UUID
1033 **
1034 ** Client is requesting a file. Send it.
1035 */
@@ -1043,10 +1236,21 @@
1043 if( rid ){
1044 send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
1045 }
1046 }
1047 }else
 
 
 
 
 
 
 
 
 
 
 
1048
1049 /* igot UUID ?ISPRIVATE?
1050 **
1051 ** Client announces that it has a particular file. If the ISPRIVATE
1052 ** argument exists and is non-zero, then the file is a private file.
@@ -1269,10 +1473,11 @@
1269 ** The client issue pragmas to try to influence the behavior of the
1270 ** server. These are requests only. Unknown pragmas are silently
1271 ** ignored.
1272 */
1273 if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
 
1274 /* pragma send-private
1275 **
1276 ** If the user has the "x" privilege (which must be set explicitly -
1277 ** it is not automatic with "a" or "s") then this pragma causes
1278 ** private information to be pulled in addition to public records.
@@ -1283,17 +1488,34 @@
1283 server_private_xfer_not_authorized();
1284 }else{
1285 xfer.syncPrivate = 1;
1286 }
1287 }
 
1288 /* pragma send-catalog
1289 **
1290 ** Send igot cards for all known artifacts.
1291 */
1292 if( blob_eq(&xfer.aToken[1], "send-catalog") ){
1293 xfer.resync = 0x7fffffff;
1294 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1295 }else
1296
1297 /* Unknown message
1298 */
1299 {
@@ -1391,16 +1613,17 @@
1391
1392 #if INTERFACE
1393 /*
1394 ** Flag options for controlling client_sync()
1395 */
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
 
1402 #endif
1403
1404 /*
1405 ** Floating-point absolute value
1406 */
@@ -1423,11 +1646,11 @@
1423 ){
1424 int go = 1; /* Loop until zero */
1425 int nCardSent = 0; /* Number of cards sent */
1426 int nCardRcvd = 0; /* Number of cards received */
1427 int nCycle = 0; /* Number of round trips to the server */
1428 int size; /* Size of a config value */
1429 int origConfigRcvMask; /* Original value of configRcvMask */
1430 int nFileRecv; /* Number of files received */
1431 int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
1432 const char *zCookie; /* Server cookie */
1433 i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
@@ -1444,13 +1667,17 @@
1444 int nRoundtrip= 0; /* Number of HTTP requests */
1445 int nArtifactSent = 0; /* Total artifacts sent */
1446 int nArtifactRcvd = 0; /* Total artifacts received */
1447 const char *zOpType = 0;/* Push, Pull, Sync, Clone */
1448 double rSkew = 0.0; /* Maximum time skew */
 
 
 
 
1449
1450 if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
1451 if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0
1452 && configRcvMask==0 && configSendMask==0 ) return 0;
1453
1454 transport_stats(0, 0, 1);
1455 socket_global_init();
1456 memset(&xfer, 0, sizeof(xfer));
@@ -1473,10 +1700,17 @@
1473
1474 /* Send the send-private pragma if we are trying to sync private data */
1475 if( syncFlags & SYNC_PRIVATE ){
1476 blob_append(&send, "pragma send-private\n", -1);
1477 }
 
 
 
 
 
 
 
1478
1479 /*
1480 ** Always begin with a clone, pull, or push message
1481 */
1482 if( syncFlags & SYNC_CLONE ){
@@ -1514,11 +1748,11 @@
1514 db_multi_exec(
1515 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
1516 );
1517 manifest_crosslink_begin();
1518
1519 /* Send make the most recently received cookie. Let the server
1520 ** figure out if this is a cookie that it cares about.
1521 */
1522 zCookie = db_get("cookie", 0);
1523 if( zCookie ){
1524 blob_appendf(&send, "cookie %s\n", zCookie);
@@ -1558,10 +1792,23 @@
1558 configure_prepare_to_receive(overwrite);
1559 }
1560 origConfigRcvMask = configRcvMask;
1561 configRcvMask = 0;
1562 }
 
 
 
 
 
 
 
 
 
 
 
 
 
1563
1564 /* Send configuration parameters being pushed */
1565 if( configSendMask ){
1566 if( zOpType==0 ) zOpType = "Push";
1567 if( configSendMask & CONFIGSET_OLDFORMAT ){
@@ -1575,10 +1822,30 @@
1575 }else{
1576 nCardSent += configure_send_group(xfer.pOut, configSendMask, 0);
1577 }
1578 configSendMask = 0;
1579 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1580
1581 /* Append randomness to the end of the message. This makes all
1582 ** messages unique so that that the login-card nonce will always
1583 ** be unique.
1584 */
@@ -1683,10 +1950,18 @@
1683 */
1684 if( blob_eq(&xfer.aToken[0],"cfile") ){
1685 xfer_accept_compressed_file(&xfer, 0, 0);
1686 nArtifactRcvd++;
1687 }else
 
 
 
 
 
 
 
 
1688
1689 /* gimme UUID
1690 **
1691 ** Server is requesting a file. If the file is a manifest, assume
1692 ** that the server will also want to know all of the content files
@@ -1730,10 +2005,28 @@
1730 if( rid ) newPhantom = 1;
1731 }
1732 remote_has(rid);
1733 }else
1734
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1735
1736 /* push SERVERCODE PRODUCTCODE
1737 **
1738 ** Should only happen in response to a clone. This message tells
1739 ** the client what product to use for the new database.
@@ -1833,10 +2126,21 @@
1833 ** The server can send pragmas to try to convey meta-information to
1834 ** the client. These are informational only. Unknown pragmas are
1835 ** silently ignored.
1836 */
1837 if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
 
 
 
 
 
 
 
 
 
 
 
1838 }else
1839
1840 /* error MESSAGE
1841 **
1842 ** Report an error and abandon the sync session.
@@ -1931,11 +2235,11 @@
1931 xfer.nDanglingFile = 0;
1932
1933 /* If we have one or more files queued to send, then go
1934 ** another round
1935 */
1936 if( xfer.nFileSent+xfer.nDeltaSent>0 ){
1937 go = 1;
1938 }
1939
1940 /* If this is a clone, the go at least two rounds */
1941 if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
1942
--- src/xfer.c
+++ src/xfer.c
@@ -282,10 +282,122 @@
282 Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]),
283 blob_size(&pXfer->aToken[1]));
284 remote_has(rid);
285 blob_reset(&content);
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 }
399
400 /*
401 ** Try to send a file as a delta against its parent.
402 ** If successful, return the number of bytes in the delta.
403 ** If we cannot generate an appropriate delta, then send
@@ -524,10 +636,47 @@
636 blob_reset(&fullContent);
637 }
638 }
639 db_reset(&q1);
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 }
678
679 /*
680 ** Send a gimme message for every phantom.
681 **
682 ** Except: do not request shunned artifacts. And do not request
@@ -833,10 +982,40 @@
982 blob_size(&content), blob_str(&content));
983 blob_reset(&content);
984 }
985 }
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
1017 /*
1018 ** Called when there is an attempt to transfer private content to and
1019 ** from a server without authorization.
1020 */
1021 static void server_private_xfer_not_authorized(void){
@@ -1026,10 +1205,24 @@
1205 @ error %T(blob_str(&xfer.err))
1206 nErr++;
1207 break;
1208 }
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
1224
1225 /* gimme UUID
1226 **
1227 ** Client is requesting a file. Send it.
1228 */
@@ -1043,10 +1236,21 @@
1236 if( rid ){
1237 send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
1238 }
1239 }
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
1252
1253 /* igot UUID ?ISPRIVATE?
1254 **
1255 ** Client announces that it has a particular file. If the ISPRIVATE
1256 ** argument exists and is non-zero, then the file is a private file.
@@ -1269,10 +1473,11 @@
1473 ** The client issue pragmas to try to influence the behavior of the
1474 ** server. These are requests only. Unknown pragmas are silently
1475 ** ignored.
1476 */
1477 if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
1478
1479 /* pragma send-private
1480 **
1481 ** If the user has the "x" privilege (which must be set explicitly -
1482 ** it is not automatic with "a" or "s") then this pragma causes
1483 ** private information to be pulled in addition to public records.
@@ -1283,17 +1488,34 @@
1488 server_private_xfer_not_authorized();
1489 }else{
1490 xfer.syncPrivate = 1;
1491 }
1492 }
1493
1494 /* pragma send-catalog
1495 **
1496 ** Send igot cards for all known artifacts.
1497 */
1498 if( blob_eq(&xfer.aToken[1], "send-catalog") ){
1499 xfer.resync = 0x7fffffff;
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 }
1517 }else
1518
1519 /* Unknown message
1520 */
1521 {
@@ -1391,16 +1613,17 @@
1613
1614 #if INTERFACE
1615 /*
1616 ** Flag options for controlling client_sync()
1617 */
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
1625 #endif
1626
1627 /*
1628 ** Floating-point absolute value
1629 */
@@ -1423,11 +1646,11 @@
1646 ){
1647 int go = 1; /* Loop until zero */
1648 int nCardSent = 0; /* Number of cards sent */
1649 int nCardRcvd = 0; /* Number of cards received */
1650 int nCycle = 0; /* Number of round trips to the server */
1651 int size; /* Size of a config value or uvfile */
1652 int origConfigRcvMask; /* Original value of configRcvMask */
1653 int nFileRecv; /* Number of files received */
1654 int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
1655 const char *zCookie; /* Server cookie */
1656 i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
@@ -1444,13 +1667,17 @@
1667 int nRoundtrip= 0; /* Number of HTTP requests */
1668 int nArtifactSent = 0; /* Total artifacts sent */
1669 int nArtifactRcvd = 0; /* Total artifacts received */
1670 const char *zOpType = 0;/* Push, Pull, Sync, Clone */
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 */
1676
1677 if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
1678 if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED))==0
1679 && configRcvMask==0 && configSendMask==0 ) return 0;
1680
1681 transport_stats(0, 0, 1);
1682 socket_global_init();
1683 memset(&xfer, 0, sizeof(xfer));
@@ -1473,10 +1700,17 @@
1700
1701 /* Send the send-private pragma if we are trying to sync private data */
1702 if( syncFlags & SYNC_PRIVATE ){
1703 blob_append(&send, "pragma send-private\n", -1);
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 }
1712
1713 /*
1714 ** Always begin with a clone, pull, or push message
1715 */
1716 if( syncFlags & SYNC_CLONE ){
@@ -1514,11 +1748,11 @@
1748 db_multi_exec(
1749 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
1750 );
1751 manifest_crosslink_begin();
1752
1753 /* Send back the most recently received cookie. Let the server
1754 ** figure out if this is a cookie that it cares about.
1755 */
1756 zCookie = db_get("cookie", 0);
1757 if( zCookie ){
1758 blob_appendf(&send, "cookie %s\n", zCookie);
@@ -1558,10 +1792,23 @@
1792 configure_prepare_to_receive(overwrite);
1793 }
1794 origConfigRcvMask = configRcvMask;
1795 configRcvMask = 0;
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 }
1810
1811 /* Send configuration parameters being pushed */
1812 if( configSendMask ){
1813 if( zOpType==0 ) zOpType = "Push";
1814 if( configSendMask & CONFIGSET_OLDFORMAT ){
@@ -1575,10 +1822,30 @@
1822 }else{
1823 nCardSent += configure_send_group(xfer.pOut, configSendMask, 0);
1824 }
1825 configSendMask = 0;
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 }
1847
1848 /* Append randomness to the end of the message. This makes all
1849 ** messages unique so that that the login-card nonce will always
1850 ** be unique.
1851 */
@@ -1683,10 +1950,18 @@
1950 */
1951 if( blob_eq(&xfer.aToken[0],"cfile") ){
1952 xfer_accept_compressed_file(&xfer, 0, 0);
1953 nArtifactRcvd++;
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
1963
1964 /* gimme UUID
1965 **
1966 ** Server is requesting a file. If the file is a manifest, assume
1967 ** that the server will also want to know all of the content files
@@ -1730,10 +2005,28 @@
2005 if( rid ) newPhantom = 1;
2006 }
2007 remote_has(rid);
2008 }else
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
2028
2029 /* push SERVERCODE PRODUCTCODE
2030 **
2031 ** Should only happen in response to a clone. This message tells
2032 ** the client what product to use for the new database.
@@ -1833,10 +2126,21 @@
2126 ** The server can send pragmas to try to convey meta-information to
2127 ** the client. These are informational only. Unknown pragmas are
2128 ** silently ignored.
2129 */
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 }
2142 }else
2143
2144 /* error MESSAGE
2145 **
2146 ** Report an error and abandon the sync session.
@@ -1931,11 +2235,11 @@
2235 xfer.nDanglingFile = 0;
2236
2237 /* If we have one or more files queued to send, then go
2238 ** another round
2239 */
2240 if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
2241 go = 1;
2242 }
2243
2244 /* If this is a clone, the go at least two rounds */
2245 if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
2246

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button