Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Merged drh's fixes new features (xfer, timeline handling, javascript based timeline highlighting) into my branch. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
15652ff081973f686974992ab86ff9d4 |
| User & Date: | aku 2007-08-29 02:55:33.000 |
Context
|
2007-08-29
| ||
| 03:22 | Patch up makemake.tcl to incorporate aku's edits, then regenerate the main.mk file. ... (check-in: b616c3d8c3 user: drh tags: trunk) | |
| 02:55 | Merged drh's fixes new features (xfer, timeline handling, javascript based timeline highlighting) into my branch. ... (check-in: 15652ff081 user: aku tags: trunk) | |
| 02:49 | Moved common large lists of header files into Make variables. ... (check-in: 4594b4e628 user: aku tags: trunk) | |
|
2007-08-28
| ||
| 03:04 | Make sure the same manifest never gets inserted into the metadata tables twice - resulting in duplicate timeline entries. ... (check-in: 18b1f6788d user: drh tags: trunk) | |
Changes
Changes to ideas.txt.
1 2 3 | Random thoughts: * Changes to manifest to support: | < > < | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
Random thoughts:
* Changes to manifest to support:
+ Trees of wiki pages and tickets
+ The ability to cap or close a branch
+ See "Extended Manifests" below
* Add the concept of "clusters" to speed the transfer of "tips"
on a sync.
* Auxiliary tables:
+ tip
+ phantom
+ mlink
+ plink
+ branch
+ tree
* Plink.isprim changed to record:
+ child is the principal descendent of parent. (1)
+ child is a branch from parent (2)
+ child uses parent as a merge (0)
* tree records
+ type (code, wiki, ticket)
+ name (for wiki and ticket only)
+ treeid
* branch records
+ treeid
+ origin_rid
+ origin_time
+ tip_rid
+ tip_time
+ color
* website can toggle isprim between principal and branch.
+ How to preserve across rebuild. A new record type?
+ How to share with other repositories
* isprim guessed using userid of parent and child. Change
in id suggests a branch. Same id suggests principal.
For a tie, go with the earliest check-in as the principal'
* Autosync mode
+ Set a preferred remote repository to use as a server
= Clone repository is the default
+ On commit, first pull. If commit baseline is not a tip
prompt user to cancel or branch. Default is cancel.
+ Push after commit
+ Automatically pull prior to update.
+ Need an "undo" capability
+ Designed to avoid branching in highly collaborative
environments.
* Archeological webpage improvements:
+ Use a small amount of CSS+javascript on timelines so that
branching structure is displayed on mouseover. On mouseover
of a checkin, highlight other checkins that are direct (non-merge)
descendents and ancestors of the mouseover checkin.
+ Timeline showing individual branches
+ Timeline shows forks and merges
+ Tags shown on timeline (maybe) and in vinfo (surely).
Extended manifests.
* normal manifest has:
C comment
D date-time
F* filename uuid
P uuid ... -- omitted for first manifest
R repository-md5sum
U user-login
Z manifest-checksum
* Change the comment on a version: -- always a leaf except in cluster
D date-time
E new-comment
P uuid -- baseline whose comment is changed
U user-login
Z checksum
-- most recent wins
* Wiki edit
A* name uuid -- zero or more attachments
C? comment
D date-time
N name -- name of the wiki page
P uuid ... -- omit for new wiki
U user-login
W uuid -- The content file
Z manifest-cksum
* Ticket edit
A* name uuid -- zero or more attachments
D date-time
N name -- name of the ticket
P uuid -- omit for new ticket
T uuid -- content of the ticket
U user-login
Z manifest-cksum
* Set or erase a tag -- most recent date wins
B* (+|-)tag uuid
C? comment
D date-time
V* (+|-) tag uuid -- + to set, - to clear.
Z manifest-cksum
-- Must have at least one B or V.
-- Tag "hidden" means do not sync
-- Tag "closed" means do not display as a leaf
* A cluster
M+ uuid
Z manifest-cksum
* Complete set of cards in a manifest files:
A filename uuid
B (+|-)branch-tag uuid
C comment
D date-time
E edited-comment
F filename uuid
N name
P uuid ...
R repository-md5sum
T uuid
U user-login
V (+|-)version-tag uuid
W uuid
Z manifest-checksum
|
Changes to src/descendents.c.
| ︙ | ︙ | |||
128 129 130 131 132 133 134 |
Stmt q;
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
style_header("Leaves");
db_prepare(&q,
| | | | > > > > > > | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
Stmt q;
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
style_header("Leaves");
db_prepare(&q,
"SELECT blob.rid, blob.uuid, datetime(event.mtime,'localtime'),"
" event.comment, event.user, 1, 1, 0"
" FROM blob, event"
" WHERE blob.rid IN"
" (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
" AND event.objid=blob.rid"
" ORDER BY event.mtime DESC"
);
www_print_timeline(&q, 0, 0, 0);
db_finalize(&q);
@ <script>
@ function xin(id){
@ }
@ function xout(id){
@ }
@ </script>
style_footer();
}
|
Changes to src/manifest.c.
| ︙ | ︙ | |||
299 300 301 302 303 304 305 |
Manifest m;
Stmt q;
if( manifest_parse(&m, pContent)==0 ){
return 0;
}
db_begin_transaction();
| > | | | | | | | | | | | | | | | | | | | > | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
Manifest m;
Stmt q;
if( manifest_parse(&m, pContent)==0 ){
return 0;
}
db_begin_transaction();
if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
for(i=0; i<m.nParent; i++){
int pid = uuid_to_rid(m.azParent[i], 1);
db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
"VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
if( i==0 ){
add_mlink(pid, 0, rid, &m);
}
}
db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
while( db_step(&q)==SQLITE_ROW ){
int cid = db_column_int(&q, 0);
add_mlink(rid, &m, cid, 0);
}
db_finalize(&q);
db_multi_exec(
"INSERT INTO event(type,mtime,objid,user,comment)"
"VALUES('ci',%.17g,%d,%Q,%Q)",
m.rDate, rid, m.zUser, m.zComment
);
}
db_end_transaction(0);
manifest_clear(&m);
return 1;
}
|
Changes to src/timeline.c.
| ︙ | ︙ | |||
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
sprintf(zShortUuid, "%.10s", zUuid);
if( g.okHistory ){
@ <a href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
}else{
@ <b>[%s(zShortUuid)]</b>
}
}
/*
** Generate a hyperlink to a diff between two versions.
*/
void hyperlink_to_diff(const char *zV1, const char *zV2){
if( g.okHistory ){
if( zV2==0 ){
@ <a href="%s(g.zBaseURL)/diff?v2=%s(zV1)">[diff]</a>
}else{
@ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
}
}
}
/*
** Output a timeline in the web format given a query. The query
** should return 4 columns:
**
| > > > > > > > > > > > > > > > > | | | | > > > > | > > > > > > > > > | > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
sprintf(zShortUuid, "%.10s", zUuid);
if( g.okHistory ){
@ <a href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
}else{
@ <b>[%s(zShortUuid)]</b>
}
}
/*
** Generate a hyperlink that invokes javascript to highlight
** a version on mouseover.
*/
void hyperlink_to_uuid_with_highlight(const char *zUuid, int id){
char zShortUuid[UUID_SIZE+1];
sprintf(zShortUuid, "%.10s", zUuid);
if( g.okHistory ){
@ <a onmouseover='hilite("m%d(id)")' onmouseout='unhilite("m%d(id)")'
@ href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
}else{
@ <b onmouseover='hilite("m%d(id)")' onmouseout='unhilite("m%d(id)")'>
@ [%s(zShortUuid)]</b>
}
}
/*
** Generate a hyperlink to a diff between two versions.
*/
void hyperlink_to_diff(const char *zV1, const char *zV2){
if( g.okHistory ){
if( zV2==0 ){
@ <a href="%s(g.zBaseURL)/diff?v2=%s(zV1)">[diff]</a>
}else{
@ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
}
}
}
/*
** Output a timeline in the web format given a query. The query
** should return 4 columns:
**
** 0. rid
** 1. UUID
** 2. Date/Time
** 3. Comment string
** 4. User
** 5. Number of non-merge children
** 6. Number of parents
** 7. True if is a leaf
*/
void www_print_timeline(
Stmt *pQuery,
char *zLastDate,
int (*xCallback)(int, Blob*),
Blob *pArg
){
char zPrevDate[20];
zPrevDate[0] = 0;
@ <table cellspacing=0 border=0 cellpadding=0>
while( db_step(pQuery)==SQLITE_ROW ){
int rid = db_column_int(pQuery, 0);
int nPChild = db_column_int(pQuery, 5);
int nParent = db_column_int(pQuery, 6);
int isLeaf = db_column_int(pQuery, 7);
const char *zDate = db_column_text(pQuery, 2);
if( xCallback ){
xCallback(rid, pArg);
}
if( memcmp(zDate, zPrevDate, 10) ){
sprintf(zPrevDate, "%.10s", zDate);
@ <tr><td colspan=3>
@ <table cellpadding=2 border=0>
@ <tr><td bgcolor="#a0b5f4" class="border1">
@ <table cellpadding=2 cellspacing=0 border=0><tr>
@ <td bgcolor="#d0d9f4" class="bkgnd1">%s(zPrevDate)</td>
@ </tr></table>
@ </td></tr></table>
@ </td></tr>
}
@ <tr id="m%d(rid)" onmouseover='xin("m%d(rid)")'
@ onmouseout='xout("m%d(rid)")'>
@ <td valign="top">%s(&zDate[11])</td>
@ <td width="20"></td>
@ <td valign="top" align="left">
hyperlink_to_uuid(db_column_text(pQuery,1));
@ %h(db_column_text(pQuery,3))
if( nParent>1 ){
Stmt q;
@ <b>Merge</b> from
db_prepare(&q,
"SELECT rid, uuid FROM plink, blob"
" WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim=0",
rid
);
while( db_step(&q)==SQLITE_ROW ){
int mrid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
hyperlink_to_uuid_with_highlight(zUuid, mrid);
}
db_finalize(&q);
}
if( nPChild>1 ){
Stmt q;
@ <b>Fork</b> to
db_prepare(&q,
"SELECT rid, uuid FROM plink, blob"
" WHERE plink.pid=%d AND blob.rid=plink.cid AND plink.isprim>0",
rid
);
while( db_step(&q)==SQLITE_ROW ){
int frid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
hyperlink_to_uuid_with_highlight(zUuid, frid);
}
db_finalize(&q);
}
if( isLeaf ){
@ <b>Leaf</b>
}
@ (by %h(db_column_text(pQuery,4)))</td></tr>
if( zLastDate ){
strcpy(zLastDate, zDate);
}
}
@ </table>
}
/*
** Generate javascript code that records the parents and children
** of the version rid.
*/
static int save_parentage_javascript(int rid, Blob *pOut){
const char *zSep;
Stmt q;
db_prepare(&q, "SELECT pid FROM plink WHERE cid=%d", rid);
zSep = "";
blob_appendf(pOut, "parentof[\"m%d\"] = [", rid);
while( db_step(&q)==SQLITE_ROW ){
int pid = db_column_int(&q, 0);
blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
zSep = ",";
}
db_finalize(&q);
blob_appendf(pOut, "];\n");
db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d", rid);
zSep = "";
blob_appendf(pOut, "childof[\"m%d\"] = [", rid);
while( db_step(&q)==SQLITE_ROW ){
int pid = db_column_int(&q, 0);
blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
zSep = ",";
}
db_finalize(&q);
blob_appendf(pOut, "];\n");
return 0;
}
/*
** WEBPAGE: timeline
*/
void page_timeline(void){
Stmt q;
char *zSQL;
Blob scriptInit;
char zDate[100];
const char *zStart = P("d");
int nEntry = atoi(PD("n","25"));
/* To view the timeline, must have permission to read project data.
*/
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
style_header("Timeline");
if( !g.okHistory &&
db_exists("SELECT 1 FROM user"
" WHERE login='anonymous'"
" AND cap LIKE '%%h%%'") ){
@ <p><b>Note:</b> You will be able to access <u>much</u> more
@ historical information if <a href="%s(g.zBaseURL)/login">login</a>.</p>
}
zSQL = mprintf(
"SELECT blob.rid, uuid, datetime(event.mtime,'localtime'), comment, user,"
" (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim=1),"
" (SELECT count(*) FROM plink WHERE cid=blob.rid),"
" NOT EXISTS (SELECT 1 FROM plink WHERE pid=blob.rid)"
" FROM event, blob"
" WHERE event.type='ci' AND blob.rid=event.objid"
);
if( zStart ){
while( isspace(zStart[0]) ){ zStart++; }
if( zStart[0] ){
zSQL = mprintf("%z AND event.mtime<=julianday(%Q, 'localtime')",
zSQL, zStart);
}
}
zSQL = mprintf("%z ORDER BY event.mtime DESC LIMIT %d", zSQL, nEntry);
db_prepare(&q, zSQL);
free(zSQL);
zDate[0] = 0;
blob_zero(&scriptInit);
www_print_timeline(&q, zDate, save_parentage_javascript, &scriptInit);
db_finalize(&q);
if( zStart==0 ){
zStart = zDate;
}
@ <script>
@ var parentof = new Object();
@ var childof = new Object();
cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));
blob_reset(&scriptInit);
@ function setall(value){
@ for(var x in parentof){
@ setone(x,value);
@ }
@ }
@ function setone(id, onoff){
@ if( parentof[id]==null ) return 0;
@ var w = document.getElementById(id);
@ var clr = onoff==1 ? "#e0e0ff" : "#ffffff";
@ if( w.backgroundColor==clr ){
@ return 0
@ }else{
@ w.style.backgroundColor = clr
@ return 1
@ }
@ }
@ function xin(id) {
@ setall(0);
@ setone(id,1);
@ set_children(id);
@ set_parents(id);
@ }
@ function xout(id) {
@ setall(0);
@ }
@ function set_parents(id){
@ var plist = parentof[id];
@ if( plist==null ) return;
@ for(var x in plist){
@ var pid = plist[x];
@ if( setone(pid,1)==1 ){
@ set_parents(pid);
@ }
@ }
@ }
@ function set_children(id){
@ var clist = childof[id];
@ if( clist==null ) return;
@ for(var x in clist){
@ var cid = clist[x];
@ if( setone(cid,1)==1 ){
@ set_children(cid);
@ }
@ }
@ }
@ function hilite(id) {
@ var x = document.getElementById(id);
@ x.style.color = "#ff0000";
@ }
@ function unhilite(id) {
@ var x = document.getElementById(id);
@ x.style.color = "#000000";
@ }
@ </script>
@ <hr>
@ <form method="GET" action="%s(g.zBaseURL)/timeline">
@ Start Date:
@ <input type="text" size="30" value="%h(zStart)" name="d">
@ Number Of Entries:
@ <input type="text" size="4" value="%d(nEntry)" name="n">
@ <br><input type="submit" value="Submit">
|
| ︙ | ︙ |
Changes to src/vfile.c.
| ︙ | ︙ | |||
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
canonical16(z, sz);
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", z);
if( rid==0 && phantomize ){
rid = content_put(0, zUuid, 0);
}
return rid;
}
/*
** Build a catalog of all files in a baseline.
** We scan the baseline file for lines of the form:
**
** F NAME UUID
**
** Each such line makes an entry in the VFILE table.
*/
void vfile_build(int vid, Blob *p){
int rid;
char *zName, *zUuid;
Stmt ins;
Blob line, token, name, uuid;
int seenHeader = 0;
db_begin_transaction();
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
db_prepare(&ins,
"INSERT INTO vfile(vid,rid,mrid,pathname) "
" VALUES(:vid,:id,:id,:name)");
db_bind_int(&ins, ":vid", vid);
while( blob_line(p, &line) ){
char *z = blob_buffer(&line);
| > > > > > > > > > > > > > > > > > > > > | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
canonical16(z, sz);
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", z);
if( rid==0 && phantomize ){
rid = content_put(0, zUuid, 0);
}
return rid;
}
/*
** Verify that an object is not a phantom. If the object is
** a phantom, output an error message and quick.
*/
void vfile_verify_not_phantom(int rid, const char *zFilename){
if( db_int(-1, "SELECT size FROM blob WHERE rid=%d", rid)<0 ){
if( zFilename ){
fossil_fatal("content missing for %s", zFilename);
}else{
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
if( zUuid ){
fossil_fatal("content missing for [%.10s]", zUuid);
}else{
fossil_panic("bad object id: %d", rid);
}
}
}
}
/*
** Build a catalog of all files in a baseline.
** We scan the baseline file for lines of the form:
**
** F NAME UUID
**
** Each such line makes an entry in the VFILE table.
*/
void vfile_build(int vid, Blob *p){
int rid;
char *zName, *zUuid;
Stmt ins;
Blob line, token, name, uuid;
int seenHeader = 0;
db_begin_transaction();
vfile_verify_not_phantom(vid, 0);
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
db_prepare(&ins,
"INSERT INTO vfile(vid,rid,mrid,pathname) "
" VALUES(:vid,:id,:id,:name)");
db_bind_int(&ins, ":vid", vid);
while( blob_line(p, &line) ){
char *z = blob_buffer(&line);
|
| ︙ | ︙ | |||
88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
blob_token(&line, &token); /* Skip the "F" token */
if( blob_token(&line, &name)==0 ) break;
if( blob_token(&line, &uuid)==0 ) break;
zName = blob_str(&name);
defossilize(zName);
zUuid = blob_str(&uuid);
rid = uuid_to_rid(zUuid, 0);
if( rid>0 && file_is_simple_pathname(zName) ){
db_bind_int(&ins, ":id", rid);
db_bind_text(&ins, ":name", zName);
db_step(&ins);
db_reset(&ins);
}
blob_reset(&name);
| > | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
blob_token(&line, &token); /* Skip the "F" token */
if( blob_token(&line, &name)==0 ) break;
if( blob_token(&line, &uuid)==0 ) break;
zName = blob_str(&name);
defossilize(zName);
zUuid = blob_str(&uuid);
rid = uuid_to_rid(zUuid, 0);
vfile_verify_not_phantom(rid, zName);
if( rid>0 && file_is_simple_pathname(zName) ){
db_bind_int(&ins, ":id", rid);
db_bind_text(&ins, ":name", zName);
db_step(&ins);
db_reset(&ins);
}
blob_reset(&name);
|
| ︙ | ︙ |
Changes to src/xfer.c.
| ︙ | ︙ | |||
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
int rid = db_int(0, "SELECT rid FROM blob WHERE uuid='%b'", pUuid);
if( rid==0 && phantomize ){
rid = content_put(0, blob_str(pUuid), 0);
}
return rid;
}
/*
** The aToken[0..nToken-1] blob array is a parse of a "file" line
** message. This routine finishes parsing that message and does
** a record insert of the file.
**
** The file line is in one of the following two forms:
| > > > > > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
int rid = db_int(0, "SELECT rid FROM blob WHERE uuid='%b'", pUuid);
if( rid==0 && phantomize ){
rid = content_put(0, blob_str(pUuid), 0);
}
return rid;
}
/*
** Remember that the other side of the connection already has a copy
** of the file rid.
*/
static void remote_has(int rid){
db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid);
}
/*
** The aToken[0..nToken-1] blob array is a parse of a "file" line
** message. This routine finishes parsing that message and does
** a record insert of the file.
**
** The file line is in one of the following two forms:
|
| ︙ | ︙ | |||
124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
blob_reset(&hash);
rid = content_put(&content, 0, 0);
if( rid==0 ){
blob_appendf(&pXfer->err, "%s", g.zErrMsg);
}else{
manifest_crosslink(rid, &content);
}
}
/*
** Try to send a file as a delta. If successful, return the number
** of bytes in the delta. If not, return zero.
**
** If srcId is specified, use it. If not, try to figure out a
| > | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
blob_reset(&hash);
rid = content_put(&content, 0, 0);
if( rid==0 ){
blob_appendf(&pXfer->err, "%s", g.zErrMsg);
}else{
manifest_crosslink(rid, &content);
}
remote_has(rid);
}
/*
** Try to send a file as a delta. If successful, return the number
** of bytes in the delta. If not, return zero.
**
** If srcId is specified, use it. If not, try to figure out a
|
| ︙ | ︙ | |||
219 220 221 222 223 224 225 |
int size = blob_size(&content);
blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
blob_append(pXfer->pOut, blob_buffer(&content), size);
pXfer->nFileSent++;
}else{
pXfer->nDeltaSent++;
}
| | > > > > > > > > > > > > > > > > > > > > > > > > > | | | < < < < < < < < < < < < < | 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
int size = blob_size(&content);
blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
blob_append(pXfer->pOut, blob_buffer(&content), size);
pXfer->nFileSent++;
}else{
pXfer->nDeltaSent++;
}
remote_has(rid);
blob_reset(&uuid);
}
/*
** Send the file identified by mid and pUuid. If that file happens
** to be a manifest, then also send all of the associated content
** files for that manifest. If the file is not a manifest, then this
** routine is the equivalent of send_file().
*/
static void send_manifest(Xfer *pXfer, int mid, Blob *pUuid, int srcId){
Stmt q2;
send_file(pXfer, mid, pUuid, srcId);
db_prepare(&q2,
"SELECT pid, uuid, fid FROM mlink, blob"
" WHERE rid=fid AND mid=%d",
mid
);
while( db_step(&q2)==SQLITE_ROW ){
int pid, fid;
Blob uuid;
pid = db_column_int(&q2, 0);
db_ephemeral_blob(&q2, 1, &uuid);
fid = db_column_int(&q2, 2);
send_file(pXfer, fid, &uuid, pid);
}
db_finalize(&q2);
}
/*
** This routine runs when either client or server is notified that
** the other side thinks rid is a leaf manifest. If we hold
** children of rid, then send them over to the other side.
*/
static void leaf_response(Xfer *pXfer, int rid){
Stmt q1;
db_prepare(&q1,
"SELECT cid, uuid FROM plink, blob"
" WHERE blob.rid=plink.cid"
" AND plink.pid=%d",
rid
);
while( db_step(&q1)==SQLITE_ROW ){
Blob uuid;
int cid;
cid = db_column_int(&q1, 0);
db_ephemeral_blob(&q1, 1, &uuid);
send_manifest(pXfer, cid, &uuid, rid);
if( blob_size(pXfer->pOut)<pXfer->mxSend ){
leaf_response(pXfer, cid);
}
}
}
/*
|
| ︙ | ︙ | |||
277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
);
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
blob_appendf(pXfer->pOut, "leaf %s\n", zUuid);
}
db_finalize(&q);
}
/*
** Sen a gimme message for every phantom.
*/
static void request_phantoms(Xfer *pXfer){
Stmt q;
db_prepare(&q, "SELECT uuid FROM phantom JOIN blob USING(rid)");
| > > > > > > > > > > > > > > > > > > > > > > > > > > > | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
);
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
blob_appendf(pXfer->pOut, "leaf %s\n", zUuid);
}
db_finalize(&q);
}
/*
** Sent leaf content for every leaf that is not found in the
** onremote table. This is intended to send leaf content for
** every leaf that is unknown on the remote end.
**
** In addition, we might send "igot" messages for a few generations of
** parents of the unknown leaves. This will speed the transmission
** of new branches.
*/
static void send_unknown_leaf_content(Xfer *pXfer){
Stmt q1;
db_prepare(&q1,
"SELECT rid, uuid FROM blob WHERE rid IN"
" (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
" AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"
);
while( db_step(&q1)==SQLITE_ROW ){
Blob uuid;
int cid;
cid = db_column_int(&q1, 0);
db_ephemeral_blob(&q1, 1, &uuid);
send_manifest(pXfer, cid, &uuid, 0);
}
db_finalize(&q1);
}
/*
** Sen a gimme message for every phantom.
*/
static void request_phantoms(Xfer *pXfer){
Stmt q;
db_prepare(&q, "SELECT uuid FROM phantom JOIN blob USING(rid)");
|
| ︙ | ︙ | |||
405 406 407 408 409 410 411 |
nErr++;
break;
}
}else
/* gimme UUID
**
| | > > | | | 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
nErr++;
break;
}
}else
/* gimme UUID
**
** Client is requesting a file. If the file is a manifest,
** the server can assume that the client also needs all content
** files associated with that manifest.
*/
if( blob_eq(&xfer.aToken[0], "gimme")
&& xfer.nToken==2
&& blob_is_uuid(&xfer.aToken[1])
){
if( isPull ){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
if( rid ){
send_manifest(&xfer, rid, &xfer.aToken[1], 0);
}
}
}else
/* igot UUID
**
** Client announces that it has a particular file.
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_uuid(&xfer.aToken[1])
){
if( isPush ){
rid_from_uuid(&xfer.aToken[1], 1);
|
| ︙ | ︙ | |||
445 446 447 448 449 450 451 |
** leaf, request it.
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "leaf")
&& blob_is_uuid(&xfer.aToken[1])
){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
| > > | | | | | > > | 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 |
** leaf, request it.
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "leaf")
&& blob_is_uuid(&xfer.aToken[1])
){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
if( rid ){
remote_has(rid);
if( isPull ){
leaf_response(&xfer, rid);
}
}else if( isPush ){
content_put(0, blob_str(&xfer.aToken[1]), 0);
}
}else
/* pull SERVERCODE PROJECTCODE
** push SERVERCODE PROJECTCODE
**
** The client wants either send or receive. The server should
** verify that the project code matches and that the server code
** does not match.
*/
if( xfer.nToken==3
&& (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
&& blob_is_uuid(&xfer.aToken[1])
&& blob_is_uuid(&xfer.aToken[2])
){
const char *zSCode;
|
| ︙ | ︙ | |||
535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
leaf_response(&xfer, rootid);
}
}else
/* login USER NONCE SIGNATURE
**
** Check for a valid login. This has to happen before anything else.
*/
if( blob_eq(&xfer.aToken[0], "login")
&& xfer.nToken==4
){
if( disableLogin ){
g.okRead = g.okWrite = 1;
}else{
| > | 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
leaf_response(&xfer, rootid);
}
}else
/* login USER NONCE SIGNATURE
**
** Check for a valid login. This has to happen before anything else.
** The client can send multiple logins. Permissions are cumulative.
*/
if( blob_eq(&xfer.aToken[0], "login")
&& xfer.nToken==4
){
if( disableLogin ){
g.okRead = g.okWrite = 1;
}else{
|
| ︙ | ︙ | |||
557 558 559 560 561 562 563 564 565 566 567 568 569 570 |
@ error bad\scommand:\s%F(blob_str(&xfer.line))
}
blobarray_reset(xfer.aToken, xfer.nToken);
}
if( isPush ){
request_phantoms(&xfer);
}
db_end_transaction(0);
}
/*
** COMMAND: test-xfer
**
** This command is used for debugging the server. There is a single
| > > > | 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 |
@ error bad\scommand:\s%F(blob_str(&xfer.line))
}
blobarray_reset(xfer.aToken, xfer.nToken);
}
if( isPush ){
request_phantoms(&xfer);
}
if( isPull ){
send_unknown_leaf_content(&xfer);
}
db_end_transaction(0);
}
/*
** COMMAND: test-xfer
**
** This command is used for debugging the server. There is a single
|
| ︙ | ︙ | |||
695 696 697 698 699 700 701 |
continue;
}
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
/* file UUID SIZE \n CONTENT
** file UUID DELTASRC SIZE \n CONTENT
**
| | | > > | | > > > > > | > > > > | > > > | > | > | 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 |
continue;
}
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
/* file UUID SIZE \n CONTENT
** file UUID DELTASRC SIZE \n CONTENT
**
** Receive a file transmitted from the server.
*/
if( blob_eq(&xfer.aToken[0],"file") ){
xfer_accept_file(&xfer);
}else
/* gimme UUID
**
** Server is requesting a file. If the file is a manifest, assume
** that the server will also want to know all of the content files
** associated with the manifest and send those too.
*/
if( blob_eq(&xfer.aToken[0], "gimme")
&& xfer.nToken==2
&& blob_is_uuid(&xfer.aToken[1])
){
nMsg++;
if( pushFlag ){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
send_manifest(&xfer, rid, &xfer.aToken[1], 0);
}
}else
/* igot UUID
**
** Server announces that it has a particular file. If this is
** not a file that we have and we are pulling, then create a
** phantom to cause this file to be requested on the next cycle.
** Always remember that the server has this file so that we do
** not transmit it by accident.
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_uuid(&xfer.aToken[1])
){
int rid = 0;
nMsg++;
if( pullFlag ){
if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
&xfer.aToken[1]) ){
rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
newPhantom = 1;
}
}
if( rid==0 ){
rid = rid_from_uuid(&xfer.aToken[1], 0);
}
remote_has(rid);
}else
/* leaf UUID
**
** Server announces that it has a particular manifest. Send
** any children of this leaf that we have if we are pushing.
** Make the leaf a phantom if we are pulling. Remember that the
** remote end has the specified UUID.
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "leaf")
&& blob_is_uuid(&xfer.aToken[1])
){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
nMsg++;
if( pushFlag && rid ){
leaf_response(&xfer, rid);
}
if( pullFlag && rid==0 ){
rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
newPhantom = 1;
}
remote_has(rid);
}else
/* push SERVERCODE PRODUCTCODE
**
** Should only happen in response to a clone. This message tells
** the client what product to use for the new database.
*/
if( blob_eq(&xfer.aToken[0],"push")
&& xfer.nToken==3
&& cloneFlag
&& blob_is_uuid(&xfer.aToken[1])
&& blob_is_uuid(&xfer.aToken[2])
){
|
| ︙ | ︙ |