diff --git a/lib/go/camli/mysqlindexer/search.go b/lib/go/camli/mysqlindexer/search.go index e3e809e5d..9364f9017 100644 --- a/lib/go/camli/mysqlindexer/search.go +++ b/lib/go/camli/mysqlindexer/search.go @@ -344,3 +344,61 @@ func (mi *Indexer) PermanodeOfSignerAttrValue(signer *blobref.BlobRef, attr, val } return blobref.Parse(row[0].(string)), nil } + +func (mi *Indexer) PathsOfSignerTarget(signer, target *blobref.BlobRef) (paths []*search.Path, err os.Error) { + keyId, err := mi.keyIdOfSigner(signer) + if err != nil { + return + } + + client, err := mi.getConnection() + if err != nil { + return + } + defer func() { + if err == nil { + mi.releaseConnection(client) + } else { + client.Close() + } + }() + + // TODO: lame %q quoting not SQL compatible; should use SQL ? bind params + err = client.Query(fmt.Sprintf("SELECT claimref, claimdate, baseref, suffix FROM path WHERE keyid=%q AND targetref=%q", + keyId, target.String())) + if err != nil { + return + } + + result, err := client.StoreResult() + if err != nil { + return + } + defer client.FreeResult() + + mostRecent := make(map[string]*search.Path) + for { + row := result.FetchRow() + if row == nil { + break + } + claimRef, claimDate, baseRef, suffix := + blobref.MustParse(row[0].(string)), row[1].(string), + blobref.MustParse(row[2].(string)), row[3].(string) + key := baseRef.String() + "/" + suffix + best, ok := mostRecent[key] + if !ok || claimDate > best.ClaimDate { + mostRecent[key] = &search.Path{ + Claim: claimRef, + ClaimDate: claimDate, + Base: baseRef, + Suffix: suffix, + } + } + } + paths = make([]*search.Path, 0) + for _, v := range mostRecent { + paths = append(paths, v) + } + return paths, nil +} diff --git a/lib/go/camli/search/handler.go b/lib/go/camli/search/handler.go index 6d2a68591..37e578293 100644 --- a/lib/go/camli/search/handler.go +++ b/lib/go/camli/search/handler.go @@ -106,6 +106,9 @@ func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { case "camli/search/signerattrvalue": sh.serveSignerAttrValue(rw, req) return + case "camli/search/signerpaths": + sh.serveSignerPaths(rw, req) + return } } @@ -423,6 +426,34 @@ func (sh *Handler) serveSignerAttrValue(rw http.ResponseWriter, req *http.Reques } } +func (sh *Handler) serveSignerPaths(rw http.ResponseWriter, req *http.Request) { + ret := jsonMap() + defer httputil.ReturnJson(rw, ret) + defer setPanicError(ret) + + signer := blobref.MustParse(mustGet(req, "signer")) + target := blobref.MustParse(mustGet(req, "target")) + paths, err := sh.index.PathsOfSignerTarget(signer, target) + if err != nil { + ret["error"] = err.String() + } else { + jpaths := []map[string]interface{}{} + for _, path := range paths { + jpaths = append(jpaths, map[string]interface{}{ + "claimRef": path.Claim.String(), + "baseRef": path.Base.String(), + "suffix": path.Suffix, + }) + } + ret["paths"] = jpaths + dr := &describeRequest{sh: sh, m: ret, wg: new(sync.WaitGroup)} + for _, path := range paths { + dr.describe(path.Base, 2) + } + dr.wg.Wait() + } +} + const camliTypePrefix = "application/json; camliType=" func setMimeType(m map[string]interface{}, mime string) { diff --git a/lib/go/camli/search/search.go b/lib/go/camli/search/search.go index e6d822be1..efa58cdb3 100644 --- a/lib/go/camli/search/search.go +++ b/lib/go/camli/search/search.go @@ -61,6 +61,12 @@ type FileInfo struct { MimeType string } +type Path struct { + Claim, Base *blobref.BlobRef + ClaimDate string + Suffix string +} + type Index interface { // dest is closed // limit is <= 0 for default. smallest possible default is 0 @@ -87,4 +93,6 @@ type Index interface { // and specific 'value', find the most recent permanode that has // a corresponding 'set-attribute' claim attached. PermanodeOfSignerAttrValue(signer *blobref.BlobRef, attr, val string) (*blobref.BlobRef, os.Error) + + PathsOfSignerTarget(signer *blobref.BlobRef, target *blobref.BlobRef) ([]*Path, os.Error) } diff --git a/server/go/camlistored/ui/camli.js b/server/go/camlistored/ui/camli.js index 28083b61d..d2219160c 100644 --- a/server/go/camlistored/ui/camli.js +++ b/server/go/camlistored/ui/camli.js @@ -101,6 +101,15 @@ function camliPermanodeOfSignerAttrValue(signer, attr, value, opts) { xhr.send(); } +// Where is the target accessed via? (paths it's at) +function camliPathsOfSignerTarget(signer, target, opts) { + var xhr = camliJsonXhr("camliPathsOfSignerTarget", opts); + var path = makeURL(Camli.config.searchRoot + "camli/search/signerpaths", + { signer: signer, target: target }); + xhr.open("GET", path, true); + xhr.send(); +} + function camliGetPermanodeClaims(permanode, opts) { var xhr = camliJsonXhr("camliGetPermanodeClaims", opts); var path = Camli.config.searchRoot + "camli/search/claims?permanode=" + diff --git a/server/go/camlistored/ui/permanode.html b/server/go/camlistored/ui/permanode.html index 935baf235..45040c7fe 100644 --- a/server/go/camlistored/ui/permanode.html +++ b/server/go/camlistored/ui/permanode.html @@ -51,6 +51,8 @@

+
+
diff --git a/server/go/camlistored/ui/permanode.js b/server/go/camlistored/ui/permanode.js index 3a6fa4fb6..071e0644a 100644 --- a/server/go/camlistored/ui/permanode.js +++ b/server/go/camlistored/ui/permanode.js @@ -473,6 +473,9 @@ function onBtnSavePublish(e) { attrcb.success = function() { console.log("success."); enabled(); + selRoots.value = ""; + suffix.value = ""; + buildPathsList(); }; attrcb.fail = function() { alert("failed to set attribute"); @@ -493,6 +496,47 @@ function onBtnSavePublish(e) { camliSigDiscovery(sigcb); } +function buildPathsList() { + var ourPermanode = getPermanodeParam(); + if (!ourPermanode) { + return; + } + var sigcb = {}; + sigcb.success = function(sigconf) { + var findpathcb = {}; + findpathcb.success = function(jres) { + var div = document.getElementById("existingPaths"); + + // TODO: there can be multiple paths in this list, but the HTML + // UI only shows one. The UI should show all, and when adding a new one + // prompt users whether they want to add to or replace the existing one. + // For now we just update the UI to show one. + // alert(JSON.stringify(jres, null, 2)); + if (jres.paths && jres.paths.length > 0) { + div.innerHTML = "Existing paths for this permanode:"; + var ul = document.createElement("ul"); + div.appendChild(ul); + for (var idx in jres.paths) { + var path = jres.paths[idx]; + var li = document.createElement("li"); + ul.appendChild(li); + li.innerHTML = " - "; + li.firstChild.href = ".?p=" + path.baseRef; + li.firstChild.innerText = path.baseRef; + li.appendChild(document.createTextNode(path.suffix)); + } + } else { + div.innerHTML = ""; + } + }; + camliPathsOfSignerTarget(sigconf.publicKeyBlobRef, ourPermanode, findpathcb); + }; + sigcb.fail = function() { + alert("sig disco failed"); + } + camliSigDiscovery(sigcb); +} + function permanodePageOnLoad(e) { var permanode = getPermanodeParam(); if (permanode) { @@ -515,6 +559,7 @@ function permanodePageOnLoad(e) { setupFilesHandlers(); buildPermanodeUi(); + buildPathsList(); } window.addEventListener("load", permanodePageOnLoad);