Fossil SCM

Merged drh's fixes new features (xfer, timeline handling, javascript based timeline highlighting) into my branch.

aku 2007-08-29 02:55 trunk merge
Commit 15652ff081973f686974992ab86ff9d40479b53a
+86 -6
--- ideas.txt
+++ ideas.txt
@@ -1,28 +1,27 @@
11
Random thoughts:
22
33
* Changes to manifest to support:
4
-
54
+ Trees of wiki pages and tickets
65
+ The ability to cap or close a branch
6
+ + See "Extended Manifests" below
77
88
* Add the concept of "clusters" to speed the transfer of "tips"
99
on a sync.
1010
1111
* Auxiliary tables:
12
-
1312
+ tip
1413
+ phantom
1514
+ mlink
1615
+ plink
1716
+ branch
1817
+ tree
1918
2019
* Plink.isprim changed to record:
21
- + child is the principal descendent of parent.
22
- + child is a branch from parent
23
- + child uses parent as a merge
20
+ + child is the principal descendent of parent. (1)
21
+ + child is a branch from parent (2)
22
+ + child uses parent as a merge (0)
2423
2524
* tree records
2625
+ type (code, wiki, ticket)
2726
+ name (for wiki and ticket only)
2827
+ treeid
@@ -38,6 +37,87 @@
3837
* website can toggle isprim between principal and branch.
3938
+ How to preserve across rebuild. A new record type?
4039
+ How to share with other repositories
4140
* isprim guessed using userid of parent and child. Change
4241
in id suggests a branch. Same id suggests principal.
43
- For a tie, go with the earliest check-in as the principal
42
+ For a tie, go with the earliest check-in as the principal'
43
+
44
+ * Autosync mode
45
+ + Set a preferred remote repository to use as a server
46
+ = Clone repository is the default
47
+ + On commit, first pull. If commit baseline is not a tip
48
+ prompt user to cancel or branch. Default is cancel.
49
+ + Push after commit
50
+ + Automatically pull prior to update.
51
+ + Need an "undo" capability
52
+ + Designed to avoid branching in highly collaborative
53
+ environments.
54
+
55
+ * Archeological webpage improvements:
56
+ + Use a small amount of CSS+javascript on timelines so that
57
+ branching structure is displayed on mouseover. On mouseover
58
+ of a checkin, highlight other checkins that are direct (non-merge)
59
+ descendents and ancestors of the mouseover checkin.
60
+ + Timeline showing individual branches
61
+ + Timeline shows forks and merges
62
+ + Tags shown on timeline (maybe) and in vinfo (surely).
63
+
64
+Extended manifests.
65
+ * normal manifest has:
66
+ C comment
67
+ D date-time
68
+ F* filename uuid
69
+ P uuid ... -- omitted for first manifest
70
+ R repository-md5sum
71
+ U user-login
72
+ Z manifest-checksum
73
+ * Change the comment on a version: -- always a leaf except in cluster
74
+ D date-time
75
+ E new-comment
76
+ P uuid -- baseline whose comment is changed
77
+ U user-login
78
+ Z checksum
79
+ -- most recent wins
80
+ * Wiki edit
81
+ A* name uuid -- zero or more attachments
82
+ C? comment
83
+ D date-time
84
+ N name -- name of the wiki page
85
+ P uuid ... -- omit for new wiki
86
+ U user-login
87
+ W uuid -- The content file
88
+ Z manifest-cksum
89
+ * Ticket edit
90
+ A* name uuid -- zero or more attachments
91
+ D date-time
92
+ N name -- name of the ticket
93
+ P uuid -- omit for new ticket
94
+ T uuid -- content of the ticket
95
+ U user-login
96
+ Z manifest-cksum
97
+ * Set or erase a tag -- most recent date wins
98
+ B* (+|-)tag uuid
99
+ C? comment
100
+ D date-time
101
+ V* (+|-) tag uuid -- + to set, - to clear.
102
+ Z manifest-cksum
103
+ -- Must have at least one B or V.
104
+ -- Tag "hidden" means do not sync
105
+ -- Tag "closed" means do not display as a leaf
106
+ * A cluster
107
+ M+ uuid
108
+ Z manifest-cksum
109
+ * Complete set of cards in a manifest files:
110
+ A filename uuid
111
+ B (+|-)branch-tag uuid
112
+ C comment
113
+ D date-time
114
+ E edited-comment
115
+ F filename uuid
116
+ N name
117
+ P uuid ...
118
+ R repository-md5sum
119
+ T uuid
120
+ U user-login
121
+ V (+|-)version-tag uuid
122
+ W uuid
123
+ Z manifest-checksum
44124
--- ideas.txt
+++ ideas.txt
@@ -1,28 +1,27 @@
1 Random thoughts:
2
3 * Changes to manifest to support:
4
5 + Trees of wiki pages and tickets
6 + The ability to cap or close a branch
 
7
8 * Add the concept of "clusters" to speed the transfer of "tips"
9 on a sync.
10
11 * Auxiliary tables:
12
13 + tip
14 + phantom
15 + mlink
16 + plink
17 + branch
18 + tree
19
20 * Plink.isprim changed to record:
21 + child is the principal descendent of parent.
22 + child is a branch from parent
23 + child uses parent as a merge
24
25 * tree records
26 + type (code, wiki, ticket)
27 + name (for wiki and ticket only)
28 + treeid
@@ -38,6 +37,87 @@
38 * website can toggle isprim between principal and branch.
39 + How to preserve across rebuild. A new record type?
40 + How to share with other repositories
41 * isprim guessed using userid of parent and child. Change
42 in id suggests a branch. Same id suggests principal.
43 For a tie, go with the earliest check-in as the principal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
--- ideas.txt
+++ ideas.txt
@@ -1,28 +1,27 @@
1 Random thoughts:
2
3 * Changes to manifest to support:
 
4 + Trees of wiki pages and tickets
5 + The ability to cap or close a branch
6 + See "Extended Manifests" below
7
8 * Add the concept of "clusters" to speed the transfer of "tips"
9 on a sync.
10
11 * Auxiliary tables:
 
12 + tip
13 + phantom
14 + mlink
15 + plink
16 + branch
17 + tree
18
19 * Plink.isprim changed to record:
20 + child is the principal descendent of parent. (1)
21 + child is a branch from parent (2)
22 + child uses parent as a merge (0)
23
24 * tree records
25 + type (code, wiki, ticket)
26 + name (for wiki and ticket only)
27 + treeid
@@ -38,6 +37,87 @@
37 * website can toggle isprim between principal and branch.
38 + How to preserve across rebuild. A new record type?
39 + How to share with other repositories
40 * isprim guessed using userid of parent and child. Change
41 in id suggests a branch. Same id suggests principal.
42 For a tie, go with the earliest check-in as the principal'
43
44 * Autosync mode
45 + Set a preferred remote repository to use as a server
46 = Clone repository is the default
47 + On commit, first pull. If commit baseline is not a tip
48 prompt user to cancel or branch. Default is cancel.
49 + Push after commit
50 + Automatically pull prior to update.
51 + Need an "undo" capability
52 + Designed to avoid branching in highly collaborative
53 environments.
54
55 * Archeological webpage improvements:
56 + Use a small amount of CSS+javascript on timelines so that
57 branching structure is displayed on mouseover. On mouseover
58 of a checkin, highlight other checkins that are direct (non-merge)
59 descendents and ancestors of the mouseover checkin.
60 + Timeline showing individual branches
61 + Timeline shows forks and merges
62 + Tags shown on timeline (maybe) and in vinfo (surely).
63
64 Extended manifests.
65 * normal manifest has:
66 C comment
67 D date-time
68 F* filename uuid
69 P uuid ... -- omitted for first manifest
70 R repository-md5sum
71 U user-login
72 Z manifest-checksum
73 * Change the comment on a version: -- always a leaf except in cluster
74 D date-time
75 E new-comment
76 P uuid -- baseline whose comment is changed
77 U user-login
78 Z checksum
79 -- most recent wins
80 * Wiki edit
81 A* name uuid -- zero or more attachments
82 C? comment
83 D date-time
84 N name -- name of the wiki page
85 P uuid ... -- omit for new wiki
86 U user-login
87 W uuid -- The content file
88 Z manifest-cksum
89 * Ticket edit
90 A* name uuid -- zero or more attachments
91 D date-time
92 N name -- name of the ticket
93 P uuid -- omit for new ticket
94 T uuid -- content of the ticket
95 U user-login
96 Z manifest-cksum
97 * Set or erase a tag -- most recent date wins
98 B* (+|-)tag uuid
99 C? comment
100 D date-time
101 V* (+|-) tag uuid -- + to set, - to clear.
102 Z manifest-cksum
103 -- Must have at least one B or V.
104 -- Tag "hidden" means do not sync
105 -- Tag "closed" means do not display as a leaf
106 * A cluster
107 M+ uuid
108 Z manifest-cksum
109 * Complete set of cards in a manifest files:
110 A filename uuid
111 B (+|-)branch-tag uuid
112 C comment
113 D date-time
114 E edited-comment
115 F filename uuid
116 N name
117 P uuid ...
118 R repository-md5sum
119 T uuid
120 U user-login
121 V (+|-)version-tag uuid
122 W uuid
123 Z manifest-checksum
124
--- src/descendents.c
+++ src/descendents.c
@@ -130,17 +130,23 @@
130130
login_check_credentials();
131131
if( !g.okRead ){ login_needed(); return; }
132132
133133
style_header("Leaves");
134134
db_prepare(&q,
135
- "SELECT blob.uuid, datetime(event.mtime,'localtime'),"
136
- " event.comment, event.user"
135
+ "SELECT blob.rid, blob.uuid, datetime(event.mtime,'localtime'),"
136
+ " event.comment, event.user, 1, 1, 0"
137137
" FROM blob, event"
138138
" WHERE blob.rid IN"
139139
" (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
140140
" AND event.objid=blob.rid"
141141
" ORDER BY event.mtime DESC"
142142
);
143
- www_print_timeline(&q, 0);
143
+ www_print_timeline(&q, 0, 0, 0);
144144
db_finalize(&q);
145
+ @ <script>
146
+ @ function xin(id){
147
+ @ }
148
+ @ function xout(id){
149
+ @ }
150
+ @ </script>
145151
style_footer();
146152
}
147153
--- src/descendents.c
+++ src/descendents.c
@@ -130,17 +130,23 @@
130 login_check_credentials();
131 if( !g.okRead ){ login_needed(); return; }
132
133 style_header("Leaves");
134 db_prepare(&q,
135 "SELECT blob.uuid, datetime(event.mtime,'localtime'),"
136 " event.comment, event.user"
137 " FROM blob, event"
138 " WHERE blob.rid IN"
139 " (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
140 " AND event.objid=blob.rid"
141 " ORDER BY event.mtime DESC"
142 );
143 www_print_timeline(&q, 0);
144 db_finalize(&q);
 
 
 
 
 
 
145 style_footer();
146 }
147
--- src/descendents.c
+++ src/descendents.c
@@ -130,17 +130,23 @@
130 login_check_credentials();
131 if( !g.okRead ){ login_needed(); return; }
132
133 style_header("Leaves");
134 db_prepare(&q,
135 "SELECT blob.rid, blob.uuid, datetime(event.mtime,'localtime'),"
136 " event.comment, event.user, 1, 1, 0"
137 " FROM blob, event"
138 " WHERE blob.rid IN"
139 " (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
140 " AND event.objid=blob.rid"
141 " ORDER BY event.mtime DESC"
142 );
143 www_print_timeline(&q, 0, 0, 0);
144 db_finalize(&q);
145 @ <script>
146 @ function xin(id){
147 @ }
148 @ function xout(id){
149 @ }
150 @ </script>
151 style_footer();
152 }
153
+21 -19
--- src/manifest.c
+++ src/manifest.c
@@ -301,28 +301,30 @@
301301
302302
if( manifest_parse(&m, pContent)==0 ){
303303
return 0;
304304
}
305305
db_begin_transaction();
306
- for(i=0; i<m.nParent; i++){
307
- int pid = uuid_to_rid(m.azParent[i], 1);
308
- db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
309
- "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
310
- if( i==0 ){
311
- add_mlink(pid, 0, rid, &m);
312
- }
313
- }
314
- db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
315
- while( db_step(&q)==SQLITE_ROW ){
316
- int cid = db_column_int(&q, 0);
317
- add_mlink(rid, &m, cid, 0);
318
- }
319
- db_finalize(&q);
320
- db_multi_exec(
321
- "INSERT INTO event(type,mtime,objid,user,comment)"
322
- "VALUES('ci',%.17g,%d,%Q,%Q)",
323
- m.rDate, rid, m.zUser, m.zComment
324
- );
306
+ if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
307
+ for(i=0; i<m.nParent; i++){
308
+ int pid = uuid_to_rid(m.azParent[i], 1);
309
+ db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
310
+ "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
311
+ if( i==0 ){
312
+ add_mlink(pid, 0, rid, &m);
313
+ }
314
+ }
315
+ db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
316
+ while( db_step(&q)==SQLITE_ROW ){
317
+ int cid = db_column_int(&q, 0);
318
+ add_mlink(rid, &m, cid, 0);
319
+ }
320
+ db_finalize(&q);
321
+ db_multi_exec(
322
+ "INSERT INTO event(type,mtime,objid,user,comment)"
323
+ "VALUES('ci',%.17g,%d,%Q,%Q)",
324
+ m.rDate, rid, m.zUser, m.zComment
325
+ );
326
+ }
325327
db_end_transaction(0);
326328
manifest_clear(&m);
327329
return 1;
328330
}
329331
--- src/manifest.c
+++ src/manifest.c
@@ -301,28 +301,30 @@
301
302 if( manifest_parse(&m, pContent)==0 ){
303 return 0;
304 }
305 db_begin_transaction();
306 for(i=0; i<m.nParent; i++){
307 int pid = uuid_to_rid(m.azParent[i], 1);
308 db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
309 "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
310 if( i==0 ){
311 add_mlink(pid, 0, rid, &m);
312 }
313 }
314 db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
315 while( db_step(&q)==SQLITE_ROW ){
316 int cid = db_column_int(&q, 0);
317 add_mlink(rid, &m, cid, 0);
318 }
319 db_finalize(&q);
320 db_multi_exec(
321 "INSERT INTO event(type,mtime,objid,user,comment)"
322 "VALUES('ci',%.17g,%d,%Q,%Q)",
323 m.rDate, rid, m.zUser, m.zComment
324 );
 
 
325 db_end_transaction(0);
326 manifest_clear(&m);
327 return 1;
328 }
329
--- src/manifest.c
+++ src/manifest.c
@@ -301,28 +301,30 @@
301
302 if( manifest_parse(&m, pContent)==0 ){
303 return 0;
304 }
305 db_begin_transaction();
306 if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
307 for(i=0; i<m.nParent; i++){
308 int pid = uuid_to_rid(m.azParent[i], 1);
309 db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
310 "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
311 if( i==0 ){
312 add_mlink(pid, 0, rid, &m);
313 }
314 }
315 db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
316 while( db_step(&q)==SQLITE_ROW ){
317 int cid = db_column_int(&q, 0);
318 add_mlink(rid, &m, cid, 0);
319 }
320 db_finalize(&q);
321 db_multi_exec(
322 "INSERT INTO event(type,mtime,objid,user,comment)"
323 "VALUES('ci',%.17g,%d,%Q,%Q)",
324 m.rDate, rid, m.zUser, m.zComment
325 );
326 }
327 db_end_transaction(0);
328 manifest_clear(&m);
329 return 1;
330 }
331
+172 -11
--- src/timeline.c
+++ src/timeline.c
@@ -37,10 +37,26 @@
3737
@ <a href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
3838
}else{
3939
@ <b>[%s(zShortUuid)]</b>
4040
}
4141
}
42
+
43
+/*
44
+** Generate a hyperlink that invokes javascript to highlight
45
+** a version on mouseover.
46
+*/
47
+void hyperlink_to_uuid_with_highlight(const char *zUuid, int id){
48
+ char zShortUuid[UUID_SIZE+1];
49
+ sprintf(zShortUuid, "%.10s", zUuid);
50
+ if( g.okHistory ){
51
+ @ <a onmouseover='hilite("m%d(id)")' onmouseout='unhilite("m%d(id)")'
52
+ @ href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
53
+ }else{
54
+ @ <b onmouseover='hilite("m%d(id)")' onmouseout='unhilite("m%d(id)")'>
55
+ @ [%s(zShortUuid)]</b>
56
+ }
57
+}
4258
4359
/*
4460
** Generate a hyperlink to a diff between two versions.
4561
*/
4662
void hyperlink_to_diff(const char *zV1, const char *zV2){
@@ -55,21 +71,37 @@
5571
5672
/*
5773
** Output a timeline in the web format given a query. The query
5874
** should return 4 columns:
5975
**
60
-** 0. UUID
61
-** 1. Date/Time
62
-** 2. Comment string
63
-** 3. User
76
+** 0. rid
77
+** 1. UUID
78
+** 2. Date/Time
79
+** 3. Comment string
80
+** 4. User
81
+** 5. Number of non-merge children
82
+** 6. Number of parents
83
+** 7. True if is a leaf
6484
*/
65
-void www_print_timeline(Stmt *pQuery, char *zLastDate){
85
+void www_print_timeline(
86
+ Stmt *pQuery,
87
+ char *zLastDate,
88
+ int (*xCallback)(int, Blob*),
89
+ Blob *pArg
90
+ ){
6691
char zPrevDate[20];
6792
zPrevDate[0] = 0;
6893
@ <table cellspacing=0 border=0 cellpadding=0>
6994
while( db_step(pQuery)==SQLITE_ROW ){
70
- const char *zDate = db_column_text(pQuery, 1);
95
+ int rid = db_column_int(pQuery, 0);
96
+ int nPChild = db_column_int(pQuery, 5);
97
+ int nParent = db_column_int(pQuery, 6);
98
+ int isLeaf = db_column_int(pQuery, 7);
99
+ const char *zDate = db_column_text(pQuery, 2);
100
+ if( xCallback ){
101
+ xCallback(rid, pArg);
102
+ }
71103
if( memcmp(zDate, zPrevDate, 10) ){
72104
sprintf(zPrevDate, "%.10s", zDate);
73105
@ <tr><td colspan=3>
74106
@ <table cellpadding=2 border=0>
75107
@ <tr><td bgcolor="#a0b5f4" class="border1">
@@ -77,30 +109,96 @@
77109
@ <td bgcolor="#d0d9f4" class="bkgnd1">%s(zPrevDate)</td>
78110
@ </tr></table>
79111
@ </td></tr></table>
80112
@ </td></tr>
81113
}
82
- @ <tr><td valign="top">%s(&zDate[11])</td>
114
+ @ <tr id="m%d(rid)" onmouseover='xin("m%d(rid)")'
115
+ @ onmouseout='xout("m%d(rid)")'>
116
+ @ <td valign="top">%s(&zDate[11])</td>
83117
@ <td width="20"></td>
84118
@ <td valign="top" align="left">
85
- hyperlink_to_uuid(db_column_text(pQuery,0));
86
- @ %h(db_column_text(pQuery,2)) (by %h(db_column_text(pQuery,3)))</td>
119
+ hyperlink_to_uuid(db_column_text(pQuery,1));
120
+ @ %h(db_column_text(pQuery,3))
121
+ if( nParent>1 ){
122
+ Stmt q;
123
+ @ <b>Merge</b> from
124
+ db_prepare(&q,
125
+ "SELECT rid, uuid FROM plink, blob"
126
+ " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim=0",
127
+ rid
128
+ );
129
+ while( db_step(&q)==SQLITE_ROW ){
130
+ int mrid = db_column_int(&q, 0);
131
+ const char *zUuid = db_column_text(&q, 1);
132
+ hyperlink_to_uuid_with_highlight(zUuid, mrid);
133
+ }
134
+ db_finalize(&q);
135
+ }
136
+ if( nPChild>1 ){
137
+ Stmt q;
138
+ @ <b>Fork</b> to
139
+ db_prepare(&q,
140
+ "SELECT rid, uuid FROM plink, blob"
141
+ " WHERE plink.pid=%d AND blob.rid=plink.cid AND plink.isprim>0",
142
+ rid
143
+ );
144
+ while( db_step(&q)==SQLITE_ROW ){
145
+ int frid = db_column_int(&q, 0);
146
+ const char *zUuid = db_column_text(&q, 1);
147
+ hyperlink_to_uuid_with_highlight(zUuid, frid);
148
+ }
149
+ db_finalize(&q);
150
+ }
151
+ if( isLeaf ){
152
+ @ <b>Leaf</b>
153
+ }
154
+ @ (by %h(db_column_text(pQuery,4)))</td></tr>
87155
if( zLastDate ){
88156
strcpy(zLastDate, zDate);
89157
}
90158
}
91159
@ </table>
92160
}
93161
162
+/*
163
+** Generate javascript code that records the parents and children
164
+** of the version rid.
165
+*/
166
+static int save_parentage_javascript(int rid, Blob *pOut){
167
+ const char *zSep;
168
+ Stmt q;
94169
170
+ db_prepare(&q, "SELECT pid FROM plink WHERE cid=%d", rid);
171
+ zSep = "";
172
+ blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
173
+ while( db_step(&q)==SQLITE_ROW ){
174
+ int pid = db_column_int(&q, 0);
175
+ blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
176
+ zSep = ",";
177
+ }
178
+ db_finalize(&q);
179
+ blob_appendf(pOut, "];\n");
180
+ db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d", rid);
181
+ zSep = "";
182
+ blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
183
+ while( db_step(&q)==SQLITE_ROW ){
184
+ int pid = db_column_int(&q, 0);
185
+ blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
186
+ zSep = ",";
187
+ }
188
+ db_finalize(&q);
189
+ blob_appendf(pOut, "];\n");
190
+ return 0;
191
+}
95192
96193
/*
97194
** WEBPAGE: timeline
98195
*/
99196
void page_timeline(void){
100197
Stmt q;
101198
char *zSQL;
199
+ Blob scriptInit;
102200
char zDate[100];
103201
const char *zStart = P("d");
104202
int nEntry = atoi(PD("n","25"));
105203
106204
/* To view the timeline, must have permission to read project data.
@@ -115,11 +213,14 @@
115213
" AND cap LIKE '%%h%%'") ){
116214
@ <p><b>Note:</b> You will be able to access <u>much</u> more
117215
@ historical information if <a href="%s(g.zBaseURL)/login">login</a>.</p>
118216
}
119217
zSQL = mprintf(
120
- "SELECT uuid, datetime(event.mtime,'localtime'), comment, user"
218
+ "SELECT blob.rid, uuid, datetime(event.mtime,'localtime'), comment, user,"
219
+ " (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim=1),"
220
+ " (SELECT count(*) FROM plink WHERE cid=blob.rid),"
221
+ " NOT EXISTS (SELECT 1 FROM plink WHERE pid=blob.rid)"
121222
" FROM event, blob"
122223
" WHERE event.type='ci' AND blob.rid=event.objid"
123224
);
124225
if( zStart ){
125226
while( isspace(zStart[0]) ){ zStart++; }
@@ -130,15 +231,75 @@
130231
}
131232
zSQL = mprintf("%z ORDER BY event.mtime DESC LIMIT %d", zSQL, nEntry);
132233
db_prepare(&q, zSQL);
133234
free(zSQL);
134235
zDate[0] = 0;
135
- www_print_timeline(&q, zDate);
236
+ blob_zero(&scriptInit);
237
+ www_print_timeline(&q, zDate, save_parentage_javascript, &scriptInit);
136238
db_finalize(&q);
137239
if( zStart==0 ){
138240
zStart = zDate;
139241
}
242
+ @ <script>
243
+ @ var parentof = new Object();
244
+ @ var childof = new Object();
245
+ cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));
246
+ blob_reset(&scriptInit);
247
+ @ function setall(value){
248
+ @ for(var x in parentof){
249
+ @ setone(x,value);
250
+ @ }
251
+ @ }
252
+ @ function setone(id, onoff){
253
+ @ if( parentof[id]==null ) return 0;
254
+ @ var w = document.getElementById(id);
255
+ @ var clr = onoff==1 ? "#e0e0ff" : "#ffffff";
256
+ @ if( w.backgroundColor==clr ){
257
+ @ return 0
258
+ @ }else{
259
+ @ w.style.backgroundColor = clr
260
+ @ return 1
261
+ @ }
262
+ @ }
263
+ @ function xin(id) {
264
+ @ setall(0);
265
+ @ setone(id,1);
266
+ @ set_children(id);
267
+ @ set_parents(id);
268
+ @ }
269
+ @ function xout(id) {
270
+ @ setall(0);
271
+ @ }
272
+ @ function set_parents(id){
273
+ @ var plist = parentof[id];
274
+ @ if( plist==null ) return;
275
+ @ for(var x in plist){
276
+ @ var pid = plist[x];
277
+ @ if( setone(pid,1)==1 ){
278
+ @ set_parents(pid);
279
+ @ }
280
+ @ }
281
+ @ }
282
+ @ function set_children(id){
283
+ @ var clist = childof[id];
284
+ @ if( clist==null ) return;
285
+ @ for(var x in clist){
286
+ @ var cid = clist[x];
287
+ @ if( setone(cid,1)==1 ){
288
+ @ set_children(cid);
289
+ @ }
290
+ @ }
291
+ @ }
292
+ @ function hilite(id) {
293
+ @ var x = document.getElementById(id);
294
+ @ x.style.color = "#ff0000";
295
+ @ }
296
+ @ function unhilite(id) {
297
+ @ var x = document.getElementById(id);
298
+ @ x.style.color = "#000000";
299
+ @ }
300
+ @ </script>
140301
@ <hr>
141302
@ <form method="GET" action="%s(g.zBaseURL)/timeline">
142303
@ Start Date:
143304
@ <input type="text" size="30" value="%h(zStart)" name="d">
144305
@ Number Of Entries:
145306
--- src/timeline.c
+++ src/timeline.c
@@ -37,10 +37,26 @@
37 @ <a href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
38 }else{
39 @ <b>[%s(zShortUuid)]</b>
40 }
41 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
43 /*
44 ** Generate a hyperlink to a diff between two versions.
45 */
46 void hyperlink_to_diff(const char *zV1, const char *zV2){
@@ -55,21 +71,37 @@
55
56 /*
57 ** Output a timeline in the web format given a query. The query
58 ** should return 4 columns:
59 **
60 ** 0. UUID
61 ** 1. Date/Time
62 ** 2. Comment string
63 ** 3. User
 
 
 
 
64 */
65 void www_print_timeline(Stmt *pQuery, char *zLastDate){
 
 
 
 
 
66 char zPrevDate[20];
67 zPrevDate[0] = 0;
68 @ <table cellspacing=0 border=0 cellpadding=0>
69 while( db_step(pQuery)==SQLITE_ROW ){
70 const char *zDate = db_column_text(pQuery, 1);
 
 
 
 
 
 
 
71 if( memcmp(zDate, zPrevDate, 10) ){
72 sprintf(zPrevDate, "%.10s", zDate);
73 @ <tr><td colspan=3>
74 @ <table cellpadding=2 border=0>
75 @ <tr><td bgcolor="#a0b5f4" class="border1">
@@ -77,30 +109,96 @@
77 @ <td bgcolor="#d0d9f4" class="bkgnd1">%s(zPrevDate)</td>
78 @ </tr></table>
79 @ </td></tr></table>
80 @ </td></tr>
81 }
82 @ <tr><td valign="top">%s(&zDate[11])</td>
 
 
83 @ <td width="20"></td>
84 @ <td valign="top" align="left">
85 hyperlink_to_uuid(db_column_text(pQuery,0));
86 @ %h(db_column_text(pQuery,2)) (by %h(db_column_text(pQuery,3)))</td>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87 if( zLastDate ){
88 strcpy(zLastDate, zDate);
89 }
90 }
91 @ </table>
92 }
93
 
 
 
 
 
 
 
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
96 /*
97 ** WEBPAGE: timeline
98 */
99 void page_timeline(void){
100 Stmt q;
101 char *zSQL;
 
102 char zDate[100];
103 const char *zStart = P("d");
104 int nEntry = atoi(PD("n","25"));
105
106 /* To view the timeline, must have permission to read project data.
@@ -115,11 +213,14 @@
115 " AND cap LIKE '%%h%%'") ){
116 @ <p><b>Note:</b> You will be able to access <u>much</u> more
117 @ historical information if <a href="%s(g.zBaseURL)/login">login</a>.</p>
118 }
119 zSQL = mprintf(
120 "SELECT uuid, datetime(event.mtime,'localtime'), comment, user"
 
 
 
121 " FROM event, blob"
122 " WHERE event.type='ci' AND blob.rid=event.objid"
123 );
124 if( zStart ){
125 while( isspace(zStart[0]) ){ zStart++; }
@@ -130,15 +231,75 @@
130 }
131 zSQL = mprintf("%z ORDER BY event.mtime DESC LIMIT %d", zSQL, nEntry);
132 db_prepare(&q, zSQL);
133 free(zSQL);
134 zDate[0] = 0;
135 www_print_timeline(&q, zDate);
 
136 db_finalize(&q);
137 if( zStart==0 ){
138 zStart = zDate;
139 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140 @ <hr>
141 @ <form method="GET" action="%s(g.zBaseURL)/timeline">
142 @ Start Date:
143 @ <input type="text" size="30" value="%h(zStart)" name="d">
144 @ Number Of Entries:
145
--- src/timeline.c
+++ src/timeline.c
@@ -37,10 +37,26 @@
37 @ <a href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
38 }else{
39 @ <b>[%s(zShortUuid)]</b>
40 }
41 }
42
43 /*
44 ** Generate a hyperlink that invokes javascript to highlight
45 ** a version on mouseover.
46 */
47 void hyperlink_to_uuid_with_highlight(const char *zUuid, int id){
48 char zShortUuid[UUID_SIZE+1];
49 sprintf(zShortUuid, "%.10s", zUuid);
50 if( g.okHistory ){
51 @ <a onmouseover='hilite("m%d(id)")' onmouseout='unhilite("m%d(id)")'
52 @ href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
53 }else{
54 @ <b onmouseover='hilite("m%d(id)")' onmouseout='unhilite("m%d(id)")'>
55 @ [%s(zShortUuid)]</b>
56 }
57 }
58
59 /*
60 ** Generate a hyperlink to a diff between two versions.
61 */
62 void hyperlink_to_diff(const char *zV1, const char *zV2){
@@ -55,21 +71,37 @@
71
72 /*
73 ** Output a timeline in the web format given a query. The query
74 ** should return 4 columns:
75 **
76 ** 0. rid
77 ** 1. UUID
78 ** 2. Date/Time
79 ** 3. Comment string
80 ** 4. User
81 ** 5. Number of non-merge children
82 ** 6. Number of parents
83 ** 7. True if is a leaf
84 */
85 void www_print_timeline(
86 Stmt *pQuery,
87 char *zLastDate,
88 int (*xCallback)(int, Blob*),
89 Blob *pArg
90 ){
91 char zPrevDate[20];
92 zPrevDate[0] = 0;
93 @ <table cellspacing=0 border=0 cellpadding=0>
94 while( db_step(pQuery)==SQLITE_ROW ){
95 int rid = db_column_int(pQuery, 0);
96 int nPChild = db_column_int(pQuery, 5);
97 int nParent = db_column_int(pQuery, 6);
98 int isLeaf = db_column_int(pQuery, 7);
99 const char *zDate = db_column_text(pQuery, 2);
100 if( xCallback ){
101 xCallback(rid, pArg);
102 }
103 if( memcmp(zDate, zPrevDate, 10) ){
104 sprintf(zPrevDate, "%.10s", zDate);
105 @ <tr><td colspan=3>
106 @ <table cellpadding=2 border=0>
107 @ <tr><td bgcolor="#a0b5f4" class="border1">
@@ -77,30 +109,96 @@
109 @ <td bgcolor="#d0d9f4" class="bkgnd1">%s(zPrevDate)</td>
110 @ </tr></table>
111 @ </td></tr></table>
112 @ </td></tr>
113 }
114 @ <tr id="m%d(rid)" onmouseover='xin("m%d(rid)")'
115 @ onmouseout='xout("m%d(rid)")'>
116 @ <td valign="top">%s(&zDate[11])</td>
117 @ <td width="20"></td>
118 @ <td valign="top" align="left">
119 hyperlink_to_uuid(db_column_text(pQuery,1));
120 @ %h(db_column_text(pQuery,3))
121 if( nParent>1 ){
122 Stmt q;
123 @ <b>Merge</b> from
124 db_prepare(&q,
125 "SELECT rid, uuid FROM plink, blob"
126 " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim=0",
127 rid
128 );
129 while( db_step(&q)==SQLITE_ROW ){
130 int mrid = db_column_int(&q, 0);
131 const char *zUuid = db_column_text(&q, 1);
132 hyperlink_to_uuid_with_highlight(zUuid, mrid);
133 }
134 db_finalize(&q);
135 }
136 if( nPChild>1 ){
137 Stmt q;
138 @ <b>Fork</b> to
139 db_prepare(&q,
140 "SELECT rid, uuid FROM plink, blob"
141 " WHERE plink.pid=%d AND blob.rid=plink.cid AND plink.isprim>0",
142 rid
143 );
144 while( db_step(&q)==SQLITE_ROW ){
145 int frid = db_column_int(&q, 0);
146 const char *zUuid = db_column_text(&q, 1);
147 hyperlink_to_uuid_with_highlight(zUuid, frid);
148 }
149 db_finalize(&q);
150 }
151 if( isLeaf ){
152 @ <b>Leaf</b>
153 }
154 @ (by %h(db_column_text(pQuery,4)))</td></tr>
155 if( zLastDate ){
156 strcpy(zLastDate, zDate);
157 }
158 }
159 @ </table>
160 }
161
162 /*
163 ** Generate javascript code that records the parents and children
164 ** of the version rid.
165 */
166 static int save_parentage_javascript(int rid, Blob *pOut){
167 const char *zSep;
168 Stmt q;
169
170 db_prepare(&q, "SELECT pid FROM plink WHERE cid=%d", rid);
171 zSep = "";
172 blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
173 while( db_step(&q)==SQLITE_ROW ){
174 int pid = db_column_int(&q, 0);
175 blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
176 zSep = ",";
177 }
178 db_finalize(&q);
179 blob_appendf(pOut, "];\n");
180 db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d", rid);
181 zSep = "";
182 blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
183 while( db_step(&q)==SQLITE_ROW ){
184 int pid = db_column_int(&q, 0);
185 blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
186 zSep = ",";
187 }
188 db_finalize(&q);
189 blob_appendf(pOut, "];\n");
190 return 0;
191 }
192
193 /*
194 ** WEBPAGE: timeline
195 */
196 void page_timeline(void){
197 Stmt q;
198 char *zSQL;
199 Blob scriptInit;
200 char zDate[100];
201 const char *zStart = P("d");
202 int nEntry = atoi(PD("n","25"));
203
204 /* To view the timeline, must have permission to read project data.
@@ -115,11 +213,14 @@
213 " AND cap LIKE '%%h%%'") ){
214 @ <p><b>Note:</b> You will be able to access <u>much</u> more
215 @ historical information if <a href="%s(g.zBaseURL)/login">login</a>.</p>
216 }
217 zSQL = mprintf(
218 "SELECT blob.rid, uuid, datetime(event.mtime,'localtime'), comment, user,"
219 " (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim=1),"
220 " (SELECT count(*) FROM plink WHERE cid=blob.rid),"
221 " NOT EXISTS (SELECT 1 FROM plink WHERE pid=blob.rid)"
222 " FROM event, blob"
223 " WHERE event.type='ci' AND blob.rid=event.objid"
224 );
225 if( zStart ){
226 while( isspace(zStart[0]) ){ zStart++; }
@@ -130,15 +231,75 @@
231 }
232 zSQL = mprintf("%z ORDER BY event.mtime DESC LIMIT %d", zSQL, nEntry);
233 db_prepare(&q, zSQL);
234 free(zSQL);
235 zDate[0] = 0;
236 blob_zero(&scriptInit);
237 www_print_timeline(&q, zDate, save_parentage_javascript, &scriptInit);
238 db_finalize(&q);
239 if( zStart==0 ){
240 zStart = zDate;
241 }
242 @ <script>
243 @ var parentof = new Object();
244 @ var childof = new Object();
245 cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));
246 blob_reset(&scriptInit);
247 @ function setall(value){
248 @ for(var x in parentof){
249 @ setone(x,value);
250 @ }
251 @ }
252 @ function setone(id, onoff){
253 @ if( parentof[id]==null ) return 0;
254 @ var w = document.getElementById(id);
255 @ var clr = onoff==1 ? "#e0e0ff" : "#ffffff";
256 @ if( w.backgroundColor==clr ){
257 @ return 0
258 @ }else{
259 @ w.style.backgroundColor = clr
260 @ return 1
261 @ }
262 @ }
263 @ function xin(id) {
264 @ setall(0);
265 @ setone(id,1);
266 @ set_children(id);
267 @ set_parents(id);
268 @ }
269 @ function xout(id) {
270 @ setall(0);
271 @ }
272 @ function set_parents(id){
273 @ var plist = parentof[id];
274 @ if( plist==null ) return;
275 @ for(var x in plist){
276 @ var pid = plist[x];
277 @ if( setone(pid,1)==1 ){
278 @ set_parents(pid);
279 @ }
280 @ }
281 @ }
282 @ function set_children(id){
283 @ var clist = childof[id];
284 @ if( clist==null ) return;
285 @ for(var x in clist){
286 @ var cid = clist[x];
287 @ if( setone(cid,1)==1 ){
288 @ set_children(cid);
289 @ }
290 @ }
291 @ }
292 @ function hilite(id) {
293 @ var x = document.getElementById(id);
294 @ x.style.color = "#ff0000";
295 @ }
296 @ function unhilite(id) {
297 @ var x = document.getElementById(id);
298 @ x.style.color = "#000000";
299 @ }
300 @ </script>
301 @ <hr>
302 @ <form method="GET" action="%s(g.zBaseURL)/timeline">
303 @ Start Date:
304 @ <input type="text" size="30" value="%h(zStart)" name="d">
305 @ Number Of Entries:
306
+21
--- src/vfile.c
+++ src/vfile.c
@@ -53,10 +53,29 @@
5353
if( rid==0 && phantomize ){
5454
rid = content_put(0, zUuid, 0);
5555
}
5656
return rid;
5757
}
58
+
59
+/*
60
+** Verify that an object is not a phantom. If the object is
61
+** a phantom, output an error message and quick.
62
+*/
63
+void vfile_verify_not_phantom(int rid, const char *zFilename){
64
+ if( db_int(-1, "SELECT size FROM blob WHERE rid=%d", rid)<0 ){
65
+ if( zFilename ){
66
+ fossil_fatal("content missing for %s", zFilename);
67
+ }else{
68
+ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
69
+ if( zUuid ){
70
+ fossil_fatal("content missing for [%.10s]", zUuid);
71
+ }else{
72
+ fossil_panic("bad object id: %d", rid);
73
+ }
74
+ }
75
+ }
76
+}
5877
5978
/*
6079
** Build a catalog of all files in a baseline.
6180
** We scan the baseline file for lines of the form:
6281
**
@@ -69,10 +88,11 @@
6988
char *zName, *zUuid;
7089
Stmt ins;
7190
Blob line, token, name, uuid;
7291
int seenHeader = 0;
7392
db_begin_transaction();
93
+ vfile_verify_not_phantom(vid, 0);
7494
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
7595
db_prepare(&ins,
7696
"INSERT INTO vfile(vid,rid,mrid,pathname) "
7797
" VALUES(:vid,:id,:id,:name)");
7898
db_bind_int(&ins, ":vid", vid);
@@ -90,10 +110,11 @@
90110
if( blob_token(&line, &uuid)==0 ) break;
91111
zName = blob_str(&name);
92112
defossilize(zName);
93113
zUuid = blob_str(&uuid);
94114
rid = uuid_to_rid(zUuid, 0);
115
+ vfile_verify_not_phantom(rid, zName);
95116
if( rid>0 && file_is_simple_pathname(zName) ){
96117
db_bind_int(&ins, ":id", rid);
97118
db_bind_text(&ins, ":name", zName);
98119
db_step(&ins);
99120
db_reset(&ins);
100121
--- src/vfile.c
+++ src/vfile.c
@@ -53,10 +53,29 @@
53 if( rid==0 && phantomize ){
54 rid = content_put(0, zUuid, 0);
55 }
56 return rid;
57 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
59 /*
60 ** Build a catalog of all files in a baseline.
61 ** We scan the baseline file for lines of the form:
62 **
@@ -69,10 +88,11 @@
69 char *zName, *zUuid;
70 Stmt ins;
71 Blob line, token, name, uuid;
72 int seenHeader = 0;
73 db_begin_transaction();
 
74 db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
75 db_prepare(&ins,
76 "INSERT INTO vfile(vid,rid,mrid,pathname) "
77 " VALUES(:vid,:id,:id,:name)");
78 db_bind_int(&ins, ":vid", vid);
@@ -90,10 +110,11 @@
90 if( blob_token(&line, &uuid)==0 ) break;
91 zName = blob_str(&name);
92 defossilize(zName);
93 zUuid = blob_str(&uuid);
94 rid = uuid_to_rid(zUuid, 0);
 
95 if( rid>0 && file_is_simple_pathname(zName) ){
96 db_bind_int(&ins, ":id", rid);
97 db_bind_text(&ins, ":name", zName);
98 db_step(&ins);
99 db_reset(&ins);
100
--- src/vfile.c
+++ src/vfile.c
@@ -53,10 +53,29 @@
53 if( rid==0 && phantomize ){
54 rid = content_put(0, zUuid, 0);
55 }
56 return rid;
57 }
58
59 /*
60 ** Verify that an object is not a phantom. If the object is
61 ** a phantom, output an error message and quick.
62 */
63 void vfile_verify_not_phantom(int rid, const char *zFilename){
64 if( db_int(-1, "SELECT size FROM blob WHERE rid=%d", rid)<0 ){
65 if( zFilename ){
66 fossil_fatal("content missing for %s", zFilename);
67 }else{
68 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
69 if( zUuid ){
70 fossil_fatal("content missing for [%.10s]", zUuid);
71 }else{
72 fossil_panic("bad object id: %d", rid);
73 }
74 }
75 }
76 }
77
78 /*
79 ** Build a catalog of all files in a baseline.
80 ** We scan the baseline file for lines of the form:
81 **
@@ -69,10 +88,11 @@
88 char *zName, *zUuid;
89 Stmt ins;
90 Blob line, token, name, uuid;
91 int seenHeader = 0;
92 db_begin_transaction();
93 vfile_verify_not_phantom(vid, 0);
94 db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
95 db_prepare(&ins,
96 "INSERT INTO vfile(vid,rid,mrid,pathname) "
97 " VALUES(:vid,:id,:id,:name)");
98 db_bind_int(&ins, ":vid", vid);
@@ -90,10 +110,11 @@
110 if( blob_token(&line, &uuid)==0 ) break;
111 zName = blob_str(&name);
112 defossilize(zName);
113 zUuid = blob_str(&uuid);
114 rid = uuid_to_rid(zUuid, 0);
115 vfile_verify_not_phantom(rid, zName);
116 if( rid>0 && file_is_simple_pathname(zName) ){
117 db_bind_int(&ins, ":id", rid);
118 db_bind_text(&ins, ":name", zName);
119 db_step(&ins);
120 db_reset(&ins);
121
+106 -33
--- src/xfer.c
+++ src/xfer.c
@@ -63,10 +63,17 @@
6363
rid = content_put(0, blob_str(pUuid), 0);
6464
}
6565
return rid;
6666
}
6767
68
+/*
69
+** Remember that the other side of the connection already has a copy
70
+** of the file rid.
71
+*/
72
+static void remote_has(int rid){
73
+ db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid);
74
+}
6875
6976
/*
7077
** The aToken[0..nToken-1] blob array is a parse of a "file" line
7178
** message. This routine finishes parsing that message and does
7279
** a record insert of the file.
@@ -126,10 +133,11 @@
126133
if( rid==0 ){
127134
blob_appendf(&pXfer->err, "%s", g.zErrMsg);
128135
}else{
129136
manifest_crosslink(rid, &content);
130137
}
138
+ remote_has(rid);
131139
}
132140
133141
/*
134142
** Try to send a file as a delta. If successful, return the number
135143
** of bytes in the delta. If not, return zero.
@@ -221,21 +229,46 @@
221229
blob_append(pXfer->pOut, blob_buffer(&content), size);
222230
pXfer->nFileSent++;
223231
}else{
224232
pXfer->nDeltaSent++;
225233
}
226
- db_multi_exec("INSERT INTO onremote VALUES(%d)", rid);
234
+ remote_has(rid);
227235
blob_reset(&uuid);
228236
}
237
+
238
+/*
239
+** Send the file identified by mid and pUuid. If that file happens
240
+** to be a manifest, then also send all of the associated content
241
+** files for that manifest. If the file is not a manifest, then this
242
+** routine is the equivalent of send_file().
243
+*/
244
+static void send_manifest(Xfer *pXfer, int mid, Blob *pUuid, int srcId){
245
+ Stmt q2;
246
+ send_file(pXfer, mid, pUuid, srcId);
247
+ db_prepare(&q2,
248
+ "SELECT pid, uuid, fid FROM mlink, blob"
249
+ " WHERE rid=fid AND mid=%d",
250
+ mid
251
+ );
252
+ while( db_step(&q2)==SQLITE_ROW ){
253
+ int pid, fid;
254
+ Blob uuid;
255
+ pid = db_column_int(&q2, 0);
256
+ db_ephemeral_blob(&q2, 1, &uuid);
257
+ fid = db_column_int(&q2, 2);
258
+ send_file(pXfer, fid, &uuid, pid);
259
+ }
260
+ db_finalize(&q2);
261
+}
229262
230263
/*
231264
** This routine runs when either client or server is notified that
232
-** the other side things rid is a leaf manifest. If we hold
265
+** the other side thinks rid is a leaf manifest. If we hold
233266
** children of rid, then send them over to the other side.
234267
*/
235268
static void leaf_response(Xfer *pXfer, int rid){
236
- Stmt q1, q2;
269
+ Stmt q1;
237270
db_prepare(&q1,
238271
"SELECT cid, uuid FROM plink, blob"
239272
" WHERE blob.rid=plink.cid"
240273
" AND plink.pid=%d",
241274
rid
@@ -244,24 +277,11 @@
244277
Blob uuid;
245278
int cid;
246279
247280
cid = db_column_int(&q1, 0);
248281
db_ephemeral_blob(&q1, 1, &uuid);
249
- send_file(pXfer, cid, &uuid, rid);
250
- db_prepare(&q2,
251
- "SELECT pid, uuid, fid FROM mlink, blob"
252
- " WHERE rid=fid AND mid=%d",
253
- cid
254
- );
255
- while( db_step(&q2)==SQLITE_ROW ){
256
- int pid, fid;
257
- pid = db_column_int(&q2, 0);
258
- db_ephemeral_blob(&q2, 1, &uuid);
259
- fid = db_column_int(&q2, 2);
260
- send_file(pXfer, fid, &uuid, pid);
261
- }
262
- db_finalize(&q2);
282
+ send_manifest(pXfer, cid, &uuid, rid);
263283
if( blob_size(pXfer->pOut)<pXfer->mxSend ){
264284
leaf_response(pXfer, cid);
265285
}
266286
}
267287
}
@@ -279,10 +299,37 @@
279299
const char *zUuid = db_column_text(&q, 0);
280300
blob_appendf(pXfer->pOut, "leaf %s\n", zUuid);
281301
}
282302
db_finalize(&q);
283303
}
304
+
305
+/*
306
+** Sent leaf content for every leaf that is not found in the
307
+** onremote table. This is intended to send leaf content for
308
+** every leaf that is unknown on the remote end.
309
+**
310
+** In addition, we might send "igot" messages for a few generations of
311
+** parents of the unknown leaves. This will speed the transmission
312
+** of new branches.
313
+*/
314
+static void send_unknown_leaf_content(Xfer *pXfer){
315
+ Stmt q1;
316
+ db_prepare(&q1,
317
+ "SELECT rid, uuid FROM blob WHERE rid IN"
318
+ " (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
319
+ " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"
320
+ );
321
+ while( db_step(&q1)==SQLITE_ROW ){
322
+ Blob uuid;
323
+ int cid;
324
+
325
+ cid = db_column_int(&q1, 0);
326
+ db_ephemeral_blob(&q1, 1, &uuid);
327
+ send_manifest(pXfer, cid, &uuid, 0);
328
+ }
329
+ db_finalize(&q1);
330
+}
284331
285332
/*
286333
** Sen a gimme message for every phantom.
287334
*/
288335
static void request_phantoms(Xfer *pXfer){
@@ -407,27 +454,29 @@
407454
}
408455
}else
409456
410457
/* gimme UUID
411458
**
412
- ** Client is requesting a file
459
+ ** Client is requesting a file. If the file is a manifest,
460
+ ** the server can assume that the client also needs all content
461
+ ** files associated with that manifest.
413462
*/
414463
if( blob_eq(&xfer.aToken[0], "gimme")
415464
&& xfer.nToken==2
416465
&& blob_is_uuid(&xfer.aToken[1])
417466
){
418467
if( isPull ){
419468
int rid = rid_from_uuid(&xfer.aToken[1], 0);
420469
if( rid ){
421
- send_file(&xfer, rid, &xfer.aToken[1], 0);
470
+ send_manifest(&xfer, rid, &xfer.aToken[1], 0);
422471
}
423472
}
424473
}else
425474
426475
/* igot UUID
427476
**
428
- ** Client announces that it has a particular file
477
+ ** Client announces that it has a particular file.
429478
*/
430479
if( xfer.nToken==2
431480
&& blob_eq(&xfer.aToken[0], "igot")
432481
&& blob_is_uuid(&xfer.aToken[1])
433482
){
@@ -447,22 +496,26 @@
447496
if( xfer.nToken==2
448497
&& blob_eq(&xfer.aToken[0], "leaf")
449498
&& blob_is_uuid(&xfer.aToken[1])
450499
){
451500
int rid = rid_from_uuid(&xfer.aToken[1], 0);
452
- if( isPull && rid ){
453
- leaf_response(&xfer, rid);
454
- }
455
- if( isPush && !rid ){
501
+ if( rid ){
502
+ remote_has(rid);
503
+ if( isPull ){
504
+ leaf_response(&xfer, rid);
505
+ }
506
+ }else if( isPush ){
456507
content_put(0, blob_str(&xfer.aToken[1]), 0);
457508
}
458509
}else
459510
460511
/* pull SERVERCODE PROJECTCODE
461512
** push SERVERCODE PROJECTCODE
462513
**
463
- ** The client wants either send or receive
514
+ ** The client wants either send or receive. The server should
515
+ ** verify that the project code matches and that the server code
516
+ ** does not match.
464517
*/
465518
if( xfer.nToken==3
466519
&& (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
467520
&& blob_is_uuid(&xfer.aToken[1])
468521
&& blob_is_uuid(&xfer.aToken[2])
@@ -537,10 +590,11 @@
537590
}else
538591
539592
/* login USER NONCE SIGNATURE
540593
**
541594
** Check for a valid login. This has to happen before anything else.
595
+ ** The client can send multiple logins. Permissions are cumulative.
542596
*/
543597
if( blob_eq(&xfer.aToken[0], "login")
544598
&& xfer.nToken==4
545599
){
546600
if( disableLogin ){
@@ -559,10 +613,13 @@
559613
blobarray_reset(xfer.aToken, xfer.nToken);
560614
}
561615
if( isPush ){
562616
request_phantoms(&xfer);
563617
}
618
+ if( isPull ){
619
+ send_unknown_leaf_content(&xfer);
620
+ }
564621
db_end_transaction(0);
565622
}
566623
567624
/*
568625
** COMMAND: test-xfer
@@ -697,53 +754,67 @@
697754
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
698755
699756
/* file UUID SIZE \n CONTENT
700757
** file UUID DELTASRC SIZE \n CONTENT
701758
**
702
- ** Receive a file transmitted from the other side
759
+ ** Receive a file transmitted from the server.
703760
*/
704761
if( blob_eq(&xfer.aToken[0],"file") ){
705762
xfer_accept_file(&xfer);
706763
}else
707764
708765
/* gimme UUID
709766
**
710
- ** Server is requesting a file
767
+ ** Server is requesting a file. If the file is a manifest, assume
768
+ ** that the server will also want to know all of the content files
769
+ ** associated with the manifest and send those too.
711770
*/
712771
if( blob_eq(&xfer.aToken[0], "gimme")
713772
&& xfer.nToken==2
714773
&& blob_is_uuid(&xfer.aToken[1])
715774
){
716775
nMsg++;
717776
if( pushFlag ){
718777
int rid = rid_from_uuid(&xfer.aToken[1], 0);
719
- send_file(&xfer, rid, &xfer.aToken[1], 0);
778
+ send_manifest(&xfer, rid, &xfer.aToken[1], 0);
720779
}
721780
}else
722781
723782
/* igot UUID
724783
**
725
- ** Server announces that it has a particular file
784
+ ** Server announces that it has a particular file. If this is
785
+ ** not a file that we have and we are pulling, then create a
786
+ ** phantom to cause this file to be requested on the next cycle.
787
+ ** Always remember that the server has this file so that we do
788
+ ** not transmit it by accident.
726789
*/
727790
if( xfer.nToken==2
728791
&& blob_eq(&xfer.aToken[0], "igot")
729792
&& blob_is_uuid(&xfer.aToken[1])
730793
){
794
+ int rid = 0;
731795
nMsg++;
732796
if( pullFlag ){
733797
if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
734798
&xfer.aToken[1]) ){
735
- content_put(0, blob_str(&xfer.aToken[1]), 0);
799
+ rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
736800
newPhantom = 1;
737801
}
738802
}
803
+ if( rid==0 ){
804
+ rid = rid_from_uuid(&xfer.aToken[1], 0);
805
+ }
806
+ remote_has(rid);
739807
}else
740808
741809
742810
/* leaf UUID
743811
**
744
- ** Server announces that it has a particular manifest
812
+ ** Server announces that it has a particular manifest. Send
813
+ ** any children of this leaf that we have if we are pushing.
814
+ ** Make the leaf a phantom if we are pulling. Remember that the
815
+ ** remote end has the specified UUID.
745816
*/
746817
if( xfer.nToken==2
747818
&& blob_eq(&xfer.aToken[0], "leaf")
748819
&& blob_is_uuid(&xfer.aToken[1])
749820
){
@@ -751,19 +822,21 @@
751822
nMsg++;
752823
if( pushFlag && rid ){
753824
leaf_response(&xfer, rid);
754825
}
755826
if( pullFlag && rid==0 ){
756
- content_put(0, blob_str(&xfer.aToken[1]), 0);
827
+ rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
757828
newPhantom = 1;
758829
}
830
+ remote_has(rid);
759831
}else
760832
761833
762834
/* push SERVERCODE PRODUCTCODE
763835
**
764
- ** Should only happen in response to a clone.
836
+ ** Should only happen in response to a clone. This message tells
837
+ ** the client what product to use for the new database.
765838
*/
766839
if( blob_eq(&xfer.aToken[0],"push")
767840
&& xfer.nToken==3
768841
&& cloneFlag
769842
&& blob_is_uuid(&xfer.aToken[1])
770843
--- src/xfer.c
+++ src/xfer.c
@@ -63,10 +63,17 @@
63 rid = content_put(0, blob_str(pUuid), 0);
64 }
65 return rid;
66 }
67
 
 
 
 
 
 
 
68
69 /*
70 ** The aToken[0..nToken-1] blob array is a parse of a "file" line
71 ** message. This routine finishes parsing that message and does
72 ** a record insert of the file.
@@ -126,10 +133,11 @@
126 if( rid==0 ){
127 blob_appendf(&pXfer->err, "%s", g.zErrMsg);
128 }else{
129 manifest_crosslink(rid, &content);
130 }
 
131 }
132
133 /*
134 ** Try to send a file as a delta. If successful, return the number
135 ** of bytes in the delta. If not, return zero.
@@ -221,21 +229,46 @@
221 blob_append(pXfer->pOut, blob_buffer(&content), size);
222 pXfer->nFileSent++;
223 }else{
224 pXfer->nDeltaSent++;
225 }
226 db_multi_exec("INSERT INTO onremote VALUES(%d)", rid);
227 blob_reset(&uuid);
228 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
230 /*
231 ** This routine runs when either client or server is notified that
232 ** the other side things rid is a leaf manifest. If we hold
233 ** children of rid, then send them over to the other side.
234 */
235 static void leaf_response(Xfer *pXfer, int rid){
236 Stmt q1, q2;
237 db_prepare(&q1,
238 "SELECT cid, uuid FROM plink, blob"
239 " WHERE blob.rid=plink.cid"
240 " AND plink.pid=%d",
241 rid
@@ -244,24 +277,11 @@
244 Blob uuid;
245 int cid;
246
247 cid = db_column_int(&q1, 0);
248 db_ephemeral_blob(&q1, 1, &uuid);
249 send_file(pXfer, cid, &uuid, rid);
250 db_prepare(&q2,
251 "SELECT pid, uuid, fid FROM mlink, blob"
252 " WHERE rid=fid AND mid=%d",
253 cid
254 );
255 while( db_step(&q2)==SQLITE_ROW ){
256 int pid, fid;
257 pid = db_column_int(&q2, 0);
258 db_ephemeral_blob(&q2, 1, &uuid);
259 fid = db_column_int(&q2, 2);
260 send_file(pXfer, fid, &uuid, pid);
261 }
262 db_finalize(&q2);
263 if( blob_size(pXfer->pOut)<pXfer->mxSend ){
264 leaf_response(pXfer, cid);
265 }
266 }
267 }
@@ -279,10 +299,37 @@
279 const char *zUuid = db_column_text(&q, 0);
280 blob_appendf(pXfer->pOut, "leaf %s\n", zUuid);
281 }
282 db_finalize(&q);
283 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
285 /*
286 ** Sen a gimme message for every phantom.
287 */
288 static void request_phantoms(Xfer *pXfer){
@@ -407,27 +454,29 @@
407 }
408 }else
409
410 /* gimme UUID
411 **
412 ** Client is requesting a file
 
 
413 */
414 if( blob_eq(&xfer.aToken[0], "gimme")
415 && xfer.nToken==2
416 && blob_is_uuid(&xfer.aToken[1])
417 ){
418 if( isPull ){
419 int rid = rid_from_uuid(&xfer.aToken[1], 0);
420 if( rid ){
421 send_file(&xfer, rid, &xfer.aToken[1], 0);
422 }
423 }
424 }else
425
426 /* igot UUID
427 **
428 ** Client announces that it has a particular file
429 */
430 if( xfer.nToken==2
431 && blob_eq(&xfer.aToken[0], "igot")
432 && blob_is_uuid(&xfer.aToken[1])
433 ){
@@ -447,22 +496,26 @@
447 if( xfer.nToken==2
448 && blob_eq(&xfer.aToken[0], "leaf")
449 && blob_is_uuid(&xfer.aToken[1])
450 ){
451 int rid = rid_from_uuid(&xfer.aToken[1], 0);
452 if( isPull && rid ){
453 leaf_response(&xfer, rid);
454 }
455 if( isPush && !rid ){
 
 
456 content_put(0, blob_str(&xfer.aToken[1]), 0);
457 }
458 }else
459
460 /* pull SERVERCODE PROJECTCODE
461 ** push SERVERCODE PROJECTCODE
462 **
463 ** The client wants either send or receive
 
 
464 */
465 if( xfer.nToken==3
466 && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
467 && blob_is_uuid(&xfer.aToken[1])
468 && blob_is_uuid(&xfer.aToken[2])
@@ -537,10 +590,11 @@
537 }else
538
539 /* login USER NONCE SIGNATURE
540 **
541 ** Check for a valid login. This has to happen before anything else.
 
542 */
543 if( blob_eq(&xfer.aToken[0], "login")
544 && xfer.nToken==4
545 ){
546 if( disableLogin ){
@@ -559,10 +613,13 @@
559 blobarray_reset(xfer.aToken, xfer.nToken);
560 }
561 if( isPush ){
562 request_phantoms(&xfer);
563 }
 
 
 
564 db_end_transaction(0);
565 }
566
567 /*
568 ** COMMAND: test-xfer
@@ -697,53 +754,67 @@
697 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
698
699 /* file UUID SIZE \n CONTENT
700 ** file UUID DELTASRC SIZE \n CONTENT
701 **
702 ** Receive a file transmitted from the other side
703 */
704 if( blob_eq(&xfer.aToken[0],"file") ){
705 xfer_accept_file(&xfer);
706 }else
707
708 /* gimme UUID
709 **
710 ** Server is requesting a file
 
 
711 */
712 if( blob_eq(&xfer.aToken[0], "gimme")
713 && xfer.nToken==2
714 && blob_is_uuid(&xfer.aToken[1])
715 ){
716 nMsg++;
717 if( pushFlag ){
718 int rid = rid_from_uuid(&xfer.aToken[1], 0);
719 send_file(&xfer, rid, &xfer.aToken[1], 0);
720 }
721 }else
722
723 /* igot UUID
724 **
725 ** Server announces that it has a particular file
 
 
 
 
726 */
727 if( xfer.nToken==2
728 && blob_eq(&xfer.aToken[0], "igot")
729 && blob_is_uuid(&xfer.aToken[1])
730 ){
 
731 nMsg++;
732 if( pullFlag ){
733 if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
734 &xfer.aToken[1]) ){
735 content_put(0, blob_str(&xfer.aToken[1]), 0);
736 newPhantom = 1;
737 }
738 }
 
 
 
 
739 }else
740
741
742 /* leaf UUID
743 **
744 ** Server announces that it has a particular manifest
 
 
 
745 */
746 if( xfer.nToken==2
747 && blob_eq(&xfer.aToken[0], "leaf")
748 && blob_is_uuid(&xfer.aToken[1])
749 ){
@@ -751,19 +822,21 @@
751 nMsg++;
752 if( pushFlag && rid ){
753 leaf_response(&xfer, rid);
754 }
755 if( pullFlag && rid==0 ){
756 content_put(0, blob_str(&xfer.aToken[1]), 0);
757 newPhantom = 1;
758 }
 
759 }else
760
761
762 /* push SERVERCODE PRODUCTCODE
763 **
764 ** Should only happen in response to a clone.
 
765 */
766 if( blob_eq(&xfer.aToken[0],"push")
767 && xfer.nToken==3
768 && cloneFlag
769 && blob_is_uuid(&xfer.aToken[1])
770
--- src/xfer.c
+++ src/xfer.c
@@ -63,10 +63,17 @@
63 rid = content_put(0, blob_str(pUuid), 0);
64 }
65 return rid;
66 }
67
68 /*
69 ** Remember that the other side of the connection already has a copy
70 ** of the file rid.
71 */
72 static void remote_has(int rid){
73 db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid);
74 }
75
76 /*
77 ** The aToken[0..nToken-1] blob array is a parse of a "file" line
78 ** message. This routine finishes parsing that message and does
79 ** a record insert of the file.
@@ -126,10 +133,11 @@
133 if( rid==0 ){
134 blob_appendf(&pXfer->err, "%s", g.zErrMsg);
135 }else{
136 manifest_crosslink(rid, &content);
137 }
138 remote_has(rid);
139 }
140
141 /*
142 ** Try to send a file as a delta. If successful, return the number
143 ** of bytes in the delta. If not, return zero.
@@ -221,21 +229,46 @@
229 blob_append(pXfer->pOut, blob_buffer(&content), size);
230 pXfer->nFileSent++;
231 }else{
232 pXfer->nDeltaSent++;
233 }
234 remote_has(rid);
235 blob_reset(&uuid);
236 }
237
238 /*
239 ** Send the file identified by mid and pUuid. If that file happens
240 ** to be a manifest, then also send all of the associated content
241 ** files for that manifest. If the file is not a manifest, then this
242 ** routine is the equivalent of send_file().
243 */
244 static void send_manifest(Xfer *pXfer, int mid, Blob *pUuid, int srcId){
245 Stmt q2;
246 send_file(pXfer, mid, pUuid, srcId);
247 db_prepare(&q2,
248 "SELECT pid, uuid, fid FROM mlink, blob"
249 " WHERE rid=fid AND mid=%d",
250 mid
251 );
252 while( db_step(&q2)==SQLITE_ROW ){
253 int pid, fid;
254 Blob uuid;
255 pid = db_column_int(&q2, 0);
256 db_ephemeral_blob(&q2, 1, &uuid);
257 fid = db_column_int(&q2, 2);
258 send_file(pXfer, fid, &uuid, pid);
259 }
260 db_finalize(&q2);
261 }
262
263 /*
264 ** This routine runs when either client or server is notified that
265 ** the other side thinks rid is a leaf manifest. If we hold
266 ** children of rid, then send them over to the other side.
267 */
268 static void leaf_response(Xfer *pXfer, int rid){
269 Stmt q1;
270 db_prepare(&q1,
271 "SELECT cid, uuid FROM plink, blob"
272 " WHERE blob.rid=plink.cid"
273 " AND plink.pid=%d",
274 rid
@@ -244,24 +277,11 @@
277 Blob uuid;
278 int cid;
279
280 cid = db_column_int(&q1, 0);
281 db_ephemeral_blob(&q1, 1, &uuid);
282 send_manifest(pXfer, cid, &uuid, rid);
 
 
 
 
 
 
 
 
 
 
 
 
 
283 if( blob_size(pXfer->pOut)<pXfer->mxSend ){
284 leaf_response(pXfer, cid);
285 }
286 }
287 }
@@ -279,10 +299,37 @@
299 const char *zUuid = db_column_text(&q, 0);
300 blob_appendf(pXfer->pOut, "leaf %s\n", zUuid);
301 }
302 db_finalize(&q);
303 }
304
305 /*
306 ** Sent leaf content for every leaf that is not found in the
307 ** onremote table. This is intended to send leaf content for
308 ** every leaf that is unknown on the remote end.
309 **
310 ** In addition, we might send "igot" messages for a few generations of
311 ** parents of the unknown leaves. This will speed the transmission
312 ** of new branches.
313 */
314 static void send_unknown_leaf_content(Xfer *pXfer){
315 Stmt q1;
316 db_prepare(&q1,
317 "SELECT rid, uuid FROM blob WHERE rid IN"
318 " (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
319 " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"
320 );
321 while( db_step(&q1)==SQLITE_ROW ){
322 Blob uuid;
323 int cid;
324
325 cid = db_column_int(&q1, 0);
326 db_ephemeral_blob(&q1, 1, &uuid);
327 send_manifest(pXfer, cid, &uuid, 0);
328 }
329 db_finalize(&q1);
330 }
331
332 /*
333 ** Sen a gimme message for every phantom.
334 */
335 static void request_phantoms(Xfer *pXfer){
@@ -407,27 +454,29 @@
454 }
455 }else
456
457 /* gimme UUID
458 **
459 ** Client is requesting a file. If the file is a manifest,
460 ** the server can assume that the client also needs all content
461 ** files associated with that manifest.
462 */
463 if( blob_eq(&xfer.aToken[0], "gimme")
464 && xfer.nToken==2
465 && blob_is_uuid(&xfer.aToken[1])
466 ){
467 if( isPull ){
468 int rid = rid_from_uuid(&xfer.aToken[1], 0);
469 if( rid ){
470 send_manifest(&xfer, rid, &xfer.aToken[1], 0);
471 }
472 }
473 }else
474
475 /* igot UUID
476 **
477 ** Client announces that it has a particular file.
478 */
479 if( xfer.nToken==2
480 && blob_eq(&xfer.aToken[0], "igot")
481 && blob_is_uuid(&xfer.aToken[1])
482 ){
@@ -447,22 +496,26 @@
496 if( xfer.nToken==2
497 && blob_eq(&xfer.aToken[0], "leaf")
498 && blob_is_uuid(&xfer.aToken[1])
499 ){
500 int rid = rid_from_uuid(&xfer.aToken[1], 0);
501 if( rid ){
502 remote_has(rid);
503 if( isPull ){
504 leaf_response(&xfer, rid);
505 }
506 }else if( isPush ){
507 content_put(0, blob_str(&xfer.aToken[1]), 0);
508 }
509 }else
510
511 /* pull SERVERCODE PROJECTCODE
512 ** push SERVERCODE PROJECTCODE
513 **
514 ** The client wants either send or receive. The server should
515 ** verify that the project code matches and that the server code
516 ** does not match.
517 */
518 if( xfer.nToken==3
519 && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
520 && blob_is_uuid(&xfer.aToken[1])
521 && blob_is_uuid(&xfer.aToken[2])
@@ -537,10 +590,11 @@
590 }else
591
592 /* login USER NONCE SIGNATURE
593 **
594 ** Check for a valid login. This has to happen before anything else.
595 ** The client can send multiple logins. Permissions are cumulative.
596 */
597 if( blob_eq(&xfer.aToken[0], "login")
598 && xfer.nToken==4
599 ){
600 if( disableLogin ){
@@ -559,10 +613,13 @@
613 blobarray_reset(xfer.aToken, xfer.nToken);
614 }
615 if( isPush ){
616 request_phantoms(&xfer);
617 }
618 if( isPull ){
619 send_unknown_leaf_content(&xfer);
620 }
621 db_end_transaction(0);
622 }
623
624 /*
625 ** COMMAND: test-xfer
@@ -697,53 +754,67 @@
754 xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
755
756 /* file UUID SIZE \n CONTENT
757 ** file UUID DELTASRC SIZE \n CONTENT
758 **
759 ** Receive a file transmitted from the server.
760 */
761 if( blob_eq(&xfer.aToken[0],"file") ){
762 xfer_accept_file(&xfer);
763 }else
764
765 /* gimme UUID
766 **
767 ** Server is requesting a file. If the file is a manifest, assume
768 ** that the server will also want to know all of the content files
769 ** associated with the manifest and send those too.
770 */
771 if( blob_eq(&xfer.aToken[0], "gimme")
772 && xfer.nToken==2
773 && blob_is_uuid(&xfer.aToken[1])
774 ){
775 nMsg++;
776 if( pushFlag ){
777 int rid = rid_from_uuid(&xfer.aToken[1], 0);
778 send_manifest(&xfer, rid, &xfer.aToken[1], 0);
779 }
780 }else
781
782 /* igot UUID
783 **
784 ** Server announces that it has a particular file. If this is
785 ** not a file that we have and we are pulling, then create a
786 ** phantom to cause this file to be requested on the next cycle.
787 ** Always remember that the server has this file so that we do
788 ** not transmit it by accident.
789 */
790 if( xfer.nToken==2
791 && blob_eq(&xfer.aToken[0], "igot")
792 && blob_is_uuid(&xfer.aToken[1])
793 ){
794 int rid = 0;
795 nMsg++;
796 if( pullFlag ){
797 if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
798 &xfer.aToken[1]) ){
799 rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
800 newPhantom = 1;
801 }
802 }
803 if( rid==0 ){
804 rid = rid_from_uuid(&xfer.aToken[1], 0);
805 }
806 remote_has(rid);
807 }else
808
809
810 /* leaf UUID
811 **
812 ** Server announces that it has a particular manifest. Send
813 ** any children of this leaf that we have if we are pushing.
814 ** Make the leaf a phantom if we are pulling. Remember that the
815 ** remote end has the specified UUID.
816 */
817 if( xfer.nToken==2
818 && blob_eq(&xfer.aToken[0], "leaf")
819 && blob_is_uuid(&xfer.aToken[1])
820 ){
@@ -751,19 +822,21 @@
822 nMsg++;
823 if( pushFlag && rid ){
824 leaf_response(&xfer, rid);
825 }
826 if( pullFlag && rid==0 ){
827 rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
828 newPhantom = 1;
829 }
830 remote_has(rid);
831 }else
832
833
834 /* push SERVERCODE PRODUCTCODE
835 **
836 ** Should only happen in response to a clone. This message tells
837 ** the client what product to use for the new database.
838 */
839 if( blob_eq(&xfer.aToken[0],"push")
840 && xfer.nToken==3
841 && cloneFlag
842 && blob_is_uuid(&xfer.aToken[1])
843

Keyboard Shortcuts

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