From 71ecb022ef7f3f71c9e06aecdde90defac7bd358 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 12 Mar 2013 19:18:56 +0000 Subject: [PATCH] Update TiddlyWeb support Lots of changes: * Make the built-in server support recipes and bags, albeit there's just one of each, called "default" * Correctly parse returned Etag to get bag of freshly PUT tiddlers * URI encoding for tiddler titles, so that tiddlers with slashes and so on work OK --- core/modules/commands/server.js | 19 ++++-- .../clientserver/wiki/html-div-tiddler.tid | 2 +- plugins/tiddlywiki/tiddlyweb/tiddlyweb.js | 63 ++++++++++++------- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/core/modules/commands/server.js b/core/modules/commands/server.js index 8f6370a1b..e31e70a37 100644 --- a/core/modules/commands/server.js +++ b/core/modules/commands/server.js @@ -44,7 +44,7 @@ Command.prototype.execute = function() { data += chunk.toString(); }); request.on("end",function() { - var prefix = "/tiddlers/"; + var prefix = "/recipes/default/tiddlers/"; if(requestPath.indexOf(prefix) === 0) { var title = decodeURIComponent(requestPath.substr(prefix.length)), fields = JSON.parse(data); @@ -65,7 +65,10 @@ Command.prototype.execute = function() { } console.log("PUT tiddler",title,fields) // self.commander.wiki.addTiddler(new $tw.Tiddler(JSON.parse(data),{title: title})); - response.writeHead(204, "OK"); + var changeCount = self.commander.wiki.getChangeCount(title).toString(); + response.writeHead(204, "OK",{ + Etag: "\"default/" + title + "/" + changeCount + ":\"" + }); response.end(); } else { response.writeHead(404); @@ -74,10 +77,11 @@ console.log("PUT tiddler",title,fields) }); break; case "DELETE": - var prefix = "/tiddlers/"; + var prefix = "/bags/default/tiddlers/"; if(requestPath.indexOf(prefix) === 0) { -console.log("DELETE tiddler",requestPath.substr(prefix.length)) -// self.commander.wiki.deleteTiddler(decodeURIComponent(requestPath.substr(prefix.length))); + var title = decodeURIComponent(requestPath.substr(prefix.length)); +console.log("DELETE tiddler",title) +// self.commander.wiki.deleteTiddler(decodeURIComponent(title)); response.writeHead(204, "OK"); response.end(); } else { @@ -94,10 +98,13 @@ console.log("DELETE tiddler",requestPath.substr(prefix.length)) response.writeHead(200, {"Content-Type": "application/json"}); text = JSON.stringify({ username: "ANONYMOUS", + space: { + recipe: "default" + }, tiddlywiki_version: $tw.version }); response.end(text,"utf8"); - } else if(requestPath === "/tiddlers.json") { + } else if(requestPath === "/recipes/default/tiddlers.json") { response.writeHead(200, {"Content-Type": "application/json"}); var tiddlers = []; $tw.wiki.forEachTiddler("title",function(title,tiddler) { diff --git a/editions/clientserver/wiki/html-div-tiddler.tid b/editions/clientserver/wiki/html-div-tiddler.tid index ae5ec2a77..754b3d0eb 100644 --- a/editions/clientserver/wiki/html-div-tiddler.tid +++ b/editions/clientserver/wiki/html-div-tiddler.tid @@ -4,6 +4,6 @@ title: $:/core/templates/html-div-tiddler This template is used for saving tiddlers as an HTML DIV tag with attributes representing the tiddler fields. This version includes the tiddler changecount as the field `revision`. --->`` revision="`<$info type='changecount'/>`"> +-->`` revision="`<$info type='changecount'/>`" bag="default">
`<$view field="text" format="htmlencoded" />`
` diff --git a/plugins/tiddlywiki/tiddlyweb/tiddlyweb.js b/plugins/tiddlywiki/tiddlyweb/tiddlyweb.js index b83027051..c425a55b8 100644 --- a/plugins/tiddlywiki/tiddlyweb/tiddlyweb.js +++ b/plugins/tiddlywiki/tiddlyweb/tiddlyweb.js @@ -76,7 +76,7 @@ var TiddlyWebSyncer = function(options) { } } }); - // Tasks are {type: "load"/"save", title:, queueTime:, lastModificationTime:} + // Tasks are {type: "load"/"save"/"delete", title:, queueTime:, lastModificationTime:} this.taskQueue = {}; // Hashmap of tasks to be performed this.taskInProgress = {}; // Hash of tasks in progress this.taskTimerId = null; // Sync timer @@ -157,9 +157,7 @@ TiddlyWebSyncer.prototype.getStatus = function(callback) { if(json) { // Record the recipe if(json.space) { - self.recipe = "recipes/" + json.space.recipe + "/"; - } else { - self.recipe = ""; + self.recipe = json.space.recipe; } // Check if we're logged in isLoggedIn = json.username !== "GUEST"; @@ -265,7 +263,7 @@ TiddlyWebSyncer.prototype.syncFromServer = function() { this.log("Retrieving skinny tiddler list"); var self = this; this.httpRequest({ - url: this.host + this.recipe + "tiddlers.json", + url: this.host + "recipes/" + this.recipe + "/tiddlers.json", callback: function(err,data) { // Check for errors if(err) { @@ -328,9 +326,13 @@ TiddlyWebSyncer.prototype.enqueueSyncTask = function(task) { // Set the timestamps on this task task.queueTime = now; task.lastModificationTime = now; - // Bail if it's not a tiddler we know about + // Fill in some tiddlerInfo if the tiddler is one we haven't seen before if(!$tw.utils.hop(this.tiddlerInfo,task.title)) { - return; + this.tiddlerInfo[task.title] = { + revision: "0", + bag: "bag-not-set", + changeCount: -1 + } } // Bail if this is a save and the tiddler is already at the changeCount that the server has if(task.type === "save" && this.wiki.getChangeCount(task.title) <= this.tiddlerInfo[task.title].changeCount) { @@ -449,7 +451,7 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) { var changeCount = this.wiki.getChangeCount(task.title); this.log("Dispatching 'save' task:",task.title); this.httpRequest({ - url: this.host + this.recipe + "tiddlers/" + task.title, + url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(task.title), type: "PUT", headers: { "Content-type": "application/json" @@ -460,9 +462,11 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) { return callback(err); } // Save the details of the new revision of the tiddler - var tiddlerInfo = self.tiddlerInfo[task.title]; + var etagInfo = self.parseEtag(request.getResponseHeader("Etag")), + tiddlerInfo = self.tiddlerInfo[task.title]; tiddlerInfo.changeCount = changeCount; - tiddlerInfo.revision = self.getRevisionFromEtag(request); + tiddlerInfo.bag = etagInfo.bag; + tiddlerInfo.revision = etagInfo.revision; // Invoke the callback callback(null); } @@ -471,7 +475,7 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) { // Load the tiddler this.log("Dispatching 'load' task:",task.title); this.httpRequest({ - url: this.host + this.recipe + "tiddlers/" + task.title, + url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(task.title), callback: function(err,data,request) { if(err) { return callback(err); @@ -485,10 +489,9 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) { } else if(task.type === "delete") { // Delete the tiddler this.log("Dispatching 'delete' task:",task.title); - var bag = this.tiddlerInfo[task.title].bag, - bagFragment = bag ? "bags/" + bag + "/tiddlers/" : "tiddlers/"; + var bag = this.tiddlerInfo[task.title].bag; this.httpRequest({ - url: this.host + bagFragment + task.title, + url: this.host + "bags/" + encodeURIComponent(bag) + "/tiddlers/" + encodeURIComponent(task.title), type: "DELETE", callback: function(err,data,request) { if(err) { @@ -566,14 +569,32 @@ TiddlyWebSyncer.prototype.convertTiddlerToTiddlyWebFormat = function(title) { }; /* -Extract the revision from the Etag header of a request +Split a TiddlyWeb Etag into its constituent parts. For example: + +``` +"system-images_public/unsyncedIcon/946151:9f11c278ccde3a3149f339f4a1db80dd4369fc04" +``` + +Note that the value includes the opening and closing double quotes. + +The parts are: + +``` +//<revision>:<hash> +``` */ -TiddlyWebSyncer.prototype.getRevisionFromEtag = function(request) { - var etag = request.getResponseHeader("Etag"); - if(etag) { - return etag.split("/")[2].split(":")[0]; // etags are like "system-images_public/unsyncedIcon/946151:9f11c278ccde3a3149f339f4a1db80dd4369fc04" +TiddlyWebSyncer.prototype.parseEtag = function(etag) { + var firstSlash = etag.indexOf("/"), + lastSlash = etag.lastIndexOf("/"), + colon = etag.lastIndexOf(":"); + if(firstSlash === -1 || lastSlash === -1 || colon === -1) { + return null; } else { - return 0; + return { + bag: decodeURIComponent(etag.substring(1,firstSlash)), + title: decodeURIComponent(etag.substring(firstSlash + 1,lastSlash)), + revision: etag.substring(lastSlash + 1,colon) + } } }; @@ -604,7 +625,7 @@ TiddlyWebSyncer.prototype.httpRequest = function(options) { // Set up the state change handler request.onreadystatechange = function() { if(this.readyState === 4) { - if(this.status === 200) { + if(this.status === 200 || this.status === 204) { // Success! options.callback(null,this.responseText,this); return;