Overview
SHA1 Hash: | 15652ff081973f686974992ab86ff9d40479b53a |
---|---|
Date: | 2007-08-29 02:55:33 |
User: | aku |
Comment: | Merged drh's fixes new features (xfer, timeline handling, javascript based timeline highlighting) into my branch. |
Timelines: | ancestors | descendants | both | trunk |
Other Links: | files | ZIP archive | manifest |
Tags And Properties
- branch=trunk inherited from [a28c83647d]
- sym-trunk inherited from [a28c83647d]
Changes
[hide diffs]Modified ideas.txt from [429a4a9a12] to [ff113a4540].
@@ -1,28 +1,27 @@ 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. - + child is a branch from parent - + child uses parent as a merge + + 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 @@ -38,6 +37,87 @@ * 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 + 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
Modified src/descendents.c from [5d5c8b9dec] to [cea178cd6b].
@@ -130,17 +130,23 @@ login_check_credentials(); if( !g.okRead ){ login_needed(); return; } style_header("Leaves"); db_prepare(&q, - "SELECT blob.uuid, datetime(event.mtime,'localtime')," - " event.comment, event.user" + "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); + www_print_timeline(&q, 0, 0, 0); db_finalize(&q); + @ <script> + @ function xin(id){ + @ } + @ function xout(id){ + @ } + @ </script> style_footer(); }
Modified src/manifest.c from [02dabff247] to [bf7dfee62a].
@@ -301,28 +301,30 @@ if( manifest_parse(&m, pContent)==0 ){ return 0; } db_begin_transaction(); - 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); + 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_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_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; }
Modified src/timeline.c from [7640f70a07] to [a0b837b0fb].
@@ -39,10 +39,26 @@ @ <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 ){ @@ -55,21 +71,37 @@ /* ** Output a timeline in the web format given a query. The query ** should return 4 columns: ** -** 0. UUID -** 1. Date/Time -** 2. Comment string -** 3. User +** 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){ +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 ){ - const char *zDate = db_column_text(pQuery, 1); + 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"> @@ -77,30 +109,96 @@ @ <td bgcolor="#d0d9f4" class="bkgnd1">%s(zPrevDate)</td> @ </tr></table> @ </td></tr></table> @ </td></tr> } - @ <tr><td valign="top">%s(&zDate[11])</td> + @ <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,0)); - @ %h(db_column_text(pQuery,2)) (by %h(db_column_text(pQuery,3)))</td> + 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. @@ -115,11 +213,14 @@ " 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 uuid, datetime(event.mtime,'localtime'), comment, user" + "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++; } @@ -130,15 +231,75 @@ } zSQL = mprintf("%z ORDER BY event.mtime DESC LIMIT %d", zSQL, nEntry); db_prepare(&q, zSQL); free(zSQL); zDate[0] = 0; - www_print_timeline(&q, zDate); + 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:
Modified src/vfile.c from [fdfe66efd6] to [aea4942364].
@@ -55,10 +55,29 @@ } 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 ** @@ -69,10 +88,11 @@ 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); @@ -90,10 +110,11 @@ 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);
Modified src/xfer.c from [caacb8871c] to [f732b233d1].
@@ -63,10 +63,17 @@ 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. @@ -126,10 +133,11 @@ 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. @@ -221,21 +229,46 @@ blob_append(pXfer->pOut, blob_buffer(&content), size); pXfer->nFileSent++; }else{ pXfer->nDeltaSent++; } - db_multi_exec("INSERT INTO onremote VALUES(%d)", rid); + 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 things rid is a leaf manifest. If we hold +** 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, q2; + Stmt q1; db_prepare(&q1, "SELECT cid, uuid FROM plink, blob" " WHERE blob.rid=plink.cid" " AND plink.pid=%d", rid @@ -244,24 +277,11 @@ Blob uuid; int cid; cid = db_column_int(&q1, 0); db_ephemeral_blob(&q1, 1, &uuid); - send_file(pXfer, cid, &uuid, rid); - db_prepare(&q2, - "SELECT pid, uuid, fid FROM mlink, blob" - " WHERE rid=fid AND mid=%d", - cid - ); - while( db_step(&q2)==SQLITE_ROW ){ - int pid, fid; - 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); + send_manifest(pXfer, cid, &uuid, rid); if( blob_size(pXfer->pOut)<pXfer->mxSend ){ leaf_response(pXfer, cid); } } } @@ -278,10 +298,37 @@ 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. */ @@ -407,27 +454,29 @@ } }else /* gimme UUID ** - ** Client is requesting a file + ** 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_file(&xfer, rid, &xfer.aToken[1], 0); + send_manifest(&xfer, rid, &xfer.aToken[1], 0); } } }else /* igot UUID ** - ** Client announces that it has a particular file + ** Client announces that it has a particular file. */ if( xfer.nToken==2 && blob_eq(&xfer.aToken[0], "igot") && blob_is_uuid(&xfer.aToken[1]) ){ @@ -447,22 +496,26 @@ 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( isPull && rid ){ - leaf_response(&xfer, rid); - } - if( isPush && !rid ){ + 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 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]) @@ -537,10 +590,11 @@ }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 ){ @@ -558,10 +612,13 @@ } blobarray_reset(xfer.aToken, xfer.nToken); } if( isPush ){ request_phantoms(&xfer); + } + if( isPull ){ + send_unknown_leaf_content(&xfer); } db_end_transaction(0); } /* @@ -697,53 +754,67 @@ 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 other side + ** 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 + ** 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_file(&xfer, rid, &xfer.aToken[1], 0); + send_manifest(&xfer, rid, &xfer.aToken[1], 0); } }else /* igot UUID ** - ** Server announces that it has a particular file + ** 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]) ){ - content_put(0, blob_str(&xfer.aToken[1]), 0); + 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 + ** 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]) ){ @@ -751,19 +822,21 @@ nMsg++; if( pushFlag && rid ){ leaf_response(&xfer, rid); } if( pullFlag && rid==0 ){ - content_put(0, blob_str(&xfer.aToken[1]), 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. + ** 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])