Monday, November 08, 2010 at 12:00 AM.
scripting2Suite.server.savePost
on savePost (username, opmltext, adratts, flupload=true) {
<<Changes
<<8/6/10; 8:04:40 AM by DW
<<At the outset, run the opmltext through the replacetable, getting rid of funny characters.
<<7/13/10; 11:36:31 PM by DW
<<If the story has been deleted throw an error.
<<7/8/10; 5:11:35 PM by DW
<<If the postdate is in a different month from the last table of contents build, rebuild it.
<<7/5/10; 8:46:53 AM by DW
<<Add fldebug boolean.
<<7/3/10; 9:25:29 AM by DW
<<Implement new protocol for new posts. If the summit of the outline doesn't have a newPostToken, or it's not valid, fail.
<<7/2/10; 8:10:12 AM by DW
<<Add logging.
<<6/27/10; 6:53:15 PM by DW
<<If the incoming OPML has an flLiveBlog attribute on the summit, copy it into the story's table. If it's true, whenever this post updates, the live-blog feed is rebuilt using its items as content.
<<6/26/10; 6:37:55 AM by DW
<<If the incoming OPML has an flNotInChronology attribute on the summit, copy it into the story's table. This should be enough so that it does not appear in the chronology.
<<6/25/10; 8:25:31 PM by DW
<<Use our visitReverseChronologic routine to exclude stories that aren't in the chronology.
<<6/14/10; 4:49:43 PM by DW
<<Save the OPML text in addition to the HTML rendering, save its url in adrstory^.urlOpml.
<<6/14/10; 4:23:21 AM by DW
<<Only set next/prev links if it's a new story.
<<6/12/10; 10:01:02 AM by DW
<<The fname doesn't change, even if the title of the story changes. This has the effect of the URL and path not changing, or the series of files we save in the file system. You can safely change the title without breaking any links.
<<6/3/10; 7:23:42 AM by DW
<<No longer save the page in the website structure -- I'm switching away from the website framework to a simple templating scheme. I don't think we get much from using WSF, and it adds a bunch of complexity. Goodbye old friend. :-)
<<6/3/10; 7:09:15 AM by DW
<<Create a folder for each story. Save versions of the OPML text in a sub-folder.
<<6/2/10; 4:02:13 PM by DW
<<Add optional param, flupload, to help with development. I don't need to upload rendered stuff to test the structuring code.
<<6/2/10; 11:07:32 AM by DW
<<We return attributes of the post in a table pointed to by adratts. We expect the caller to add these atts to the summit of the story, and they will be present the next time the story is saved. This is the way we exchange state info with the client.
<<6/1/10; 3:13:34 PM by DW
<<Created.
local (adrdata = scripting2suite.inituser (username), startticks = clock.ticks (), fname, xstruct, now = clock.now (), fldebug = true);
local (adrsystemdata = scripting2suite.init (), flnewstory = false, atts, title, postdate, adrpage, adrstory, urlpage, storyserialnum);
bundle { //save off debugging data
if fldebug {
bundle { //if not defined (scratchpad.savepostdata)
new (tabletype, @scratchpad.savepostdata)};
scratchpad.savepostdata.username = username;
scratchpad.savepostdata.opmltext = opmltext}};
bundle { //run opmltext through the replaceTable, 8/6/10 by DW
opmltext = string.multiplereplaceall (opmltext, @xml.rss.data.replaceTable, false)};
bundle { //get postdate, etc
local (adr);
on decode (s) {
return (xml.entitydecode (s, true))};
xml.compile (opmltext, @xstruct);
<<scratchpad.xstruct = xstruct
local (adropml = xml.getaddress (@xstruct, "opml"));
local (adrbody = xml.getaddress (adropml, "body"));
local (adrsummit = xml.getaddress (adrbody, "outline"));
atts = adrsummit^.["/atts"];
bundle { //set postdate
if defined (atts.created) {
postdate = date (atts.created)}
else {
atts.created = date.netstandardstring (now);
postdate = now};
if defined (atts.whenCreated) { //old name for this
delete (@atts.whenCreated)}};
title = decode (atts.text);
fname = scripting2Suite.server.titleToFilename (title)};
bundle { //save data in calendar structure
local (adrcalendarday = mainresponder.calendar.getdayaddress (@adrdata^.calendar, postdate));
bundle { //set storySerialNum
if defined (atts.storySerialNum) {
storySerialNum = atts.storySerialNum}
else {
storySerialNum = adrdata^.stats.storySerialNum++;
atts.storySerialNum = storySerialNum}};
adrstory = @adrcalendarday^.[string.padwithzeros (storySerialNum, 5)];
if not defined (adrstory^) {
local (adrnewposttoken); //7/3/10 by DW
bundle { //set adrnewposttoken, if the newPostToken att is not present or invalid --> fail
if not defined (atts.newPostToken) {
scripterror ("Can't create a new post because the outline doesn't have a \"new post token.\"")};
adrnewposttoken = @adrdata^.stats.newPostTokens.[atts.newPostToken];
if not defined (adrnewposttoken^) {
scripterror ("Can't create a new post because the outline's \"new post token\" is not defined.")};
delete (@atts.newPostToken)}; //the token is consumed
new (tabletype, adrstory);
adrnewposttoken^ = string.popfilefromaddress (adrstory); //7/3/10 by DW
flnewstory = true; //6/14/10 by DW
adrstory^.versionSerialNum = 1;
adrstory^.ctSaves = 0;
adrstory^.whenFirstSave = now;
adrstory^.flFirstBuild = true}; //so we know to build the previous page, 6/12/10 by DW
if not defined (adrstory^.fname) {
adrstory^.fname = fname};
if not defined (adrstory^.flFirstBuild) { //6/12/10 by DW
adrstory^.flFirstBuild = false};
adrstory^.opmltext = string (opmltext);
adrstory^.xstruct = xstruct;
adrstory^.title = title;
adrstory^.postDate = postdate;
adrstory^.whenLastSave = now;
adrstory^.ctSaves++;
bundle { //flNotInChronology & flPreview, 6/26/10 by DW
if defined (atts.flNotInChronology) {
adrstory^.flNotInChronology = boolean (atts.flNotInChronology)};
if defined (atts.flPreview) {
adrstory^.flPreview = boolean (atts.flPreview)}};
bundle { //flLiveBlog, 6/27/10 by DW
if defined (atts.flLiveBlog) {
adrstory^.flLiveBlog = boolean (atts.flLiveBlog)}};
bundle { //flVisibleSubtext, 6/30/10 by DW
if defined (atts.flVisibleSubtext) {
adrstory^.flVisibleSubtext = boolean (atts.flVisibleSubtext)}}};
bundle { //if story has been deleted, throw an error, 7/13/10 by DW
if defined (adrstory^.flDeleted) {
if adrstory^.flDeleted {
scripterror ("Can't update the post because it has been deleted.")}}};
bundle { //set url
local (baseurl = scripting2Suite.server.getBaseUrl (username));
<<if adrdata^.prefs.ftp.enabled
<<baseurl = adrdata^.prefs.ftp.url
<<else
<<if adrsystemdata^.prefs.ftp.enabled
<<baseurl = adrsystemdata^.prefs.baseurl + username + "/"
<<else
<<if adrdata^.prefs.localfolder.enabled
<<baseurl = adrdata^.prefs.localfolder.url
<<else
<<baseurl = adrsystemdata^.prefs.localfolder.url
adrstory^.path = "stories/" + file.getdatepath ("/", postdate) + adrstory^.fname + adrsystemdata^.prefs.htmlFileExtension;
adrstory^.url = baseurl + adrstory^.path;
adrstory^.urlOpml = string.popsuffix (adrstory^.url) + ".opml";
bundle { //domainToMap, 6/29/10 by DW
if defined (atts.domainToMap) {
adrstory^.domain = atts.domainToMap;
scripting2Suite.server.mapDomainToStory (adrstory)}}};
bundle { //set next/prev links
if flnewstory { //6/14/10 by DW
adrstory^.adrnext = "";
adrstory^.adrprev = "";
on visit (adritem) {
if adritem == adrstory {
return (true)};
adrstory^.adrprev = string.popfilefromaddress (adritem); //6/11/10 by DW
adritem^.adrnext = string.popfilefromaddress (adrstory); //6/11/10 by DW
return (false)};
scripting2Suite.server.visitReverseChronologic (@adrdata^.calendar, @visit)}};
bundle { //build the page, save it
scripting2Suite.server.buildStoryPage (username, adrstory, flupload);
atts.url = adrstory^.url; //it gets passed back to the client
adrdata^.stats.urlLastStoryBuild = adrstory^.url;
adrdata^.stats.whenLastStoryBuild = now;
adrdata^.stats.ctStoryBuilds++};
bundle { //do the ancillary builds in a new thread, 6/9/10 by DW
thread.callscript (@scripting2Suite.server.buildAfterSave, {username, adrstory})};
bundle { //rebuild opmltext, save it out in various places
bundle { //last changes to atts
if not defined (atts.ctSaves) {
atts.ctSaves = 1}
else {
atts.ctSaves = number (atts.ctSaves) + 1};
atts.whenLastSave = date.netstandardstring (now)};
bundle { //attach the revised atts to the summit of xstruct
local (adrsummit = scripting2Suite.server.getStorySummit (adrstory));
adrsummit^.["/atts"] = atts};
bundle { //rebuild the opmltext with the revised atts
adrstory^.opmltext = xml.decompile (@adrstory^.xstruct)};
bundle { //hack the XML to make it valid, per Marc Barrot
local (badX = "<?xml encoding=\"ISO-8859-1\" version=\"1.0\"?>");
local (goodX = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
adrstory^.opmltext = string.replace (adrstory^.opmltext, badX, goodX)};
bundle { //save opmltext in folder structure
local (pc = file.getpathchar ());
local (folder = adrdata^.prefs.opmlfolder + "stories" + pc + file.getdatepath (theDate:postdate) + adrstory^.fname + pc);
local (f = folder + string.padwithzeros (adrstory^.versionSerialNum++, 3) + ".opml");
file.surefilepath (f);
file.writewholefile (f, adrstory^.opmltext);
adrstory^.f = f};
bundle { //save the opml of the page, 6/14/10 by DW
local (opmlpath = string.popsuffix (adrstory^.path) + ".opml");
scripting2Suite.writeStaticFile (username, opmlpath, adrstory^.opmltext)}};
bundle { //logging, 7/2/10 by DW
local (eventtype, htmltext = "User \"" + username + "\" ");
if flnewstory {
eventtype = "New post";
htmltext = htmltext + "created a <a href=\"" + adrstory^.url + "\">new post</a> entitled \"" + adrstory^.title + ".\""}
else {
eventtype = "Edit post";
htmltext = htmltext + "updated an <a href=\"" + adrstory^.url + "\">existing post</a> entitled \"" + adrstory^.title + ".\""};
scripting2Suite.server.addToLog (scripting2Info.name, eventtype, htmltext, startticks)};
bundle { //see if TOC needs rebuilding, 7/8/10 by DW
if not date.samemonth (adrdata^.stats.whenLastTocBuild, postdate) {
thread.callscript (@scripting2Suite.server.buildTableOfContents, {username})}};
adratts^ = atts; //pass the updated atts to the editor
return (true)};
bundle { //test code
savepost ("davewiner", scratchpad.savepostdata.opmltext, @scratchpad.savepostdata.atts)}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.