blog.herby.sk

Sus scrofa f. sapiens
writebacks

Categories

Archives

26 July 2009
Sunday

More editor functionality

Now I'm going to scare you ;-)
I got over to another important milestone: multiple applications, possibility to create new applications and editor able to edit any of the applications (boot, and the editor, of course, are also applications, so you can edit them while they run). Everything was done inside the platform, without external action.
Inside configure.js I also reused the trick I used before the multiapp editor and so the files needed to deploy are only two. First of them is the filesaving script fs.rb, which wasn't changed at all, the second is large configure.js. Here it is:

// ====== variables to configure ======
var pwd = "undefined";
var domain = "appdomain.net";

// please configure the path to save script in saveRawSource below if needed
// ====== verbatim copy of appjet._boot definition from boot.js ====== begin ======
appjet._boot = {
run: 
function (amd) {
    appjet.mainDomain = amd;
    if (request.headers.Host == "rescue." + amd) {
        var app = request.params.app;
        var appdata = new this.storable("obj-! " + app);
        if (appdata.doesNotExist) {
            printp("rescue: unknown app: ", app);
            response.stop();
        }
        var rescue = appdata.getString("server.bak");
        this.saveAppSource(app, rescue);
        printp("Last known good code of ", app, " restored.", BR(), CODE(rescue));
        printp(link("http://edit."+amd+"/?app="+app, "Go edit it!"));
        response.stop();
    } else if (request.headers.Host == "edit." + amd) {
        import("lib-! edit");
    } else if (request.headers.Host == "create." + amd) {
        import("lib-! create");
    } else {
        import("lib-! worker");
    }
}
,

saveRawSource: 
function (files) {
    var info = {pwd:new this.storable("obj-root").getString("pwd")};
    for (var p in files) {
        info[p + ".js"] = files[p];
    }
    wpost("http://staticdomain.net/fs.rb", info);
}
,

storable: 
function (id) {
    if (appjet._native.storage_getById(id).status != 0) {
        return {doesNotExist:true};
    }
    this.getString = function (key) {
        return appjet._native.storage_get(id, key).value;
    };
    this.putString = function (key, value) {
        return appjet._native.storage_put(id, key, "string", value).status == 0;
    };
}
,

saveAppSource: 
function (name, code, doBackup) {
    var appdata = new this.storable("obj-! " + name);
    if (doBackup) {
        appdata.putString("server.bak", appdata.getString("server"));
    }
    var files = {};
    files["lib-! " + name] = "/* appjet:version 0.1 */\n" + "/* appjet:library */\n" + code;
    files[name] = "/* appjet:version 0.1 */\n" + code;
    this.saveRawSource(files);
    appdata.putString("server", code);
}
,

};
// ====== verbatim copy of appjet._boot definition from boot.js ====== end ======

switch (request.path) {
case '/1':
    var bootBuilder = ["// (c) 2009, Herbert Voj\u010d\xedk"+"""
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

(appjet._boot = {
"""];
    eachProperty(appjet._boot, function(name, f) {
        bootBuilder.push(name, ": ", f, ",\n\n");
    });
    bootBuilder.push("}).run(\"", domain, "\");");
    
    // ====== Storage dump, edited ====== begin ======
       (function () {
           var _an = appjet._native;
           _an.storage_create("obj-! create");
           _an.storage_create("obj-! worker");
           _an.storage_create("obj-! edit");
           _an.storage_create("obj-! boot");
           _an.storage_coll_create("coll-LyqehQFl7");
           _an.storage_put("obj-! create", "server.bak", "string", "// (c) 2009, Herbert Voj\u010d\xedk \r\n// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)\r\n\r\nimport(\"storage\");\r\n(function(app){\r\n\r\nif (!app) {\r\n    return;\r\n}\r\nvar id = \"obj-! \"+app;\r\nif (appjet._native.storage_getById(id).status == 0) {\r\n    printp(\"Application \", B(CODE(app)), \" already exists.\");\r\n    return;\r\n}\r\nif (!app.match(/[a-z][a-z0-9-]+[a-z0-9]/)) {\r\n    printp(\"Application name must\",UL(\r\n        LI(\"be at least three characters long,\"),\r\n        LI(\"begin with smallcaps letter,\"),\r\n        LI(\"end with smallcaps letter or digit,\"),\r\n        LI(\"and contain only smallcaps letters, digits, or minus sign/dash (-) characters.\")\r\n    ));\r\n    return;\r\n}\r\n\r\nappjet._native.storage_create(id);\r\nvar appdata = getStorable(id);\r\nappdata[\"server.bak\"] = 'printp(\"Hello, world!\");';\r\nstorage.apps.add(appdata);\r\nresponse.redirect(\"http://rescue.\"+appjet.mainDomain+\"/?app=\"+app);\r\n\r\n})(request.params.app);\r\n\r\nprint(FORM(\"Supply application name:\", INPUT({name:'app'}), INPUT({type:'submit'})));\r\n");
           _an.storage_put("obj-root", "pwd", "string", pwd);
           _an.storage_put("obj-root", "apps", "object", "coll-LyqehQFl7");
           _an.storage_put("obj-! worker", "server.bak", "string", "printp(\"Hello, world!\");");
           _an.storage_put("obj-! edit", "server.bak", "string", "// (c) 2009, Herbert Voj\u010d\xedk \r\n// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php) \r\n\r\nvar app = request.params.app;\r\nvar appdata = new appjet._boot.storable(\"obj-! \"+app);\r\nif (appdata.doesNotExist) { printp(\"editor: unknown app: \", app); response.stop(); }\r\n\r\nvar _code = request.params.code;\r\nif (_code) {\r\n\tappjet._boot.saveAppSource(app, _code, true);\r\n\tresponse.redirect(request.path+\"?app=\"+encodeURIComponent(app));\r\n}\r\n\r\nprintp(\r\nB(app,\".\",appjet.mainDomain),BR(),\r\nB({style:'color:#0c0'},\"/* appjet:server */\"),BR(),\r\nFORM({method:'post', action:request.path},\r\n\tTEXTAREA({name:'code', style:'width:100%;height:80ex'},\r\n\t\thtml(appdata.getString(\"server\"))\r\n\t),\r\n\tINPUT({type:'hidden', name:'app', value:app}),\r\n\tINPUT({type:'submit'})\r\n));\r\n");
           _an.storage_put("obj-! boot", "server.bak", "string", bootBuilder.join(""));
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! worker");
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! edit");
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! boot");
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! create");
       })();
    // ====== Storage dump, edited ====== end ======
    
    printp("Storage configured. Backup version of boot, create, edit and worker apps included; you may restore them afterwards using rescue."+domain+".");
    printp("MD5 of password is:", BR(), md5(pwd), BR(),
        "Please update the hash in the filesaving script and proceed to ", link("/2", "step 2"), " to restore boot.js.");
    break;

case '/2':
    var appdata = new appjet._boot.storable("obj-! boot");
    var rescue = appdata.getString("server.bak");
    appjet._boot.saveAppSource("boot", rescue);
    printp("boot.js restored.");
    printp("You may stop running configure.js and start running boot.js.");
    break;

default:
    printp(H1("Platform configuration"));
    printp(BIG(link("/1", "Start the wizard")));
    break;
}

Bulk data for the configure.js were prepared by the platform itself (the code of the appjet._boot as well as storage dump were presented by the platform and were only copypasted into configure.js; the storage dump was edited to be minimal needed).
The structure of boot.js being

(appjet._boot={
... big inline object ..
... where all functionality resides ...
}).run("appdomain.net");

allowed for a cool metacircularity, which is present in configure.js as well.

  1. There is verbatim copy of the big inline object in the code of configure.js, as a living code, not as a string.
  2. Then, due to structure of boot.js, its complete source is prepared in a few lines using reflexion on this live instance.
  3. Then, later, this live instance is used to write its own code that was created from it into the boot.js file.

I simply like it.

Configure now has three pages, path / leads to welcome screen with link to /1 which fills the storage and emits the password hash, but does not save anything (the hash may need to be updated, the saves would not work anyway). From there link goes to path /2 which then simply rescues (using the live instance of appjet._boot) the boot application, thus creating boot.js file.

After successful configuring, there are no applications except integrated rescue, but it can be used to unleash the rest, which is only present as backup in storage: edit, create, worker.

Platform now recognizes multiple urls.

  • rescue.appdomain.net/?app=xxx rescues application xxx from backup (every app has now its own backup).
  • edit.appdomain.net/?app=xxx edits application xxx. Application "edit" must be present, rescue it first.
  • create.appdomain.net/ creates new application. Application "create" must be present, rescue it first.
  • anythingelse.appdomain.net/ runs the application "worker" (properly parsing hostname was postponed for later, multiapp editor was more urgent). Of course, the worker also must be rescued first, or you get an error.

I also think that now my idea of using import instead of eval is much more clearly visible, in the appjet._boot; in the way how saveAppSource saves the sourcecode and how run runs multiple applications.

To finish it, one of the cutest things is this: running rescue.appdomain.net/?app=boot is probably doomed. If you screwed your boot.js, it probably won't be working to rescue itself. No problem, stop the server (it's useless with broken boot.js anyway), start it with configure.js and run just the second step...
and it wasn't planned, it just came out this way.

writebacks...

  • dvbportal wrote
    Very nice. I feel, I have to try it out myself.
    I see, you use your own storage identifiers. I didn't know that it is possible to do so. However, it leads to predictable storage access and a possible security problem when used in the public. But for now it doesn't matter. I am curious to see what comes next.
  • herby wrote
    As soon as I know an id, it's the problem in any shared storage solution that is not properly virtualized. For example Vezquex's upload app created urls based on ids. So if I see the url, i can simply gain access to the object and do whatever I see fit with it. It is false sense of security based on the fact the "ids are not publicly known".
    And proper virtualization (translating any id on the way there in the arguments and on the way back in the return values by overriding every low-level storage access method) solves this, cleanly.
    There is another, very nasty problem with constructed ids - whenever I recreate the object with same id, garbage collecting of data goes mad and the object disappears "randomly" after some time. With random ids, this is not an issue, every id is created only once, and later eventually garbage-collected. So I probably move away from constructed ids, or somehow make sure it is not created more times.
    Well, I thought of what should come next. I am probably hitting "microkernel versus monolithic" dilemma here. appjet._boot does too much things which are not needed in every case (in most cases writing a file is not needed functionality). In general, I'm in favour of microkernel, so maybe boot.js will get much leaner and some things moved to lib, but then I lose the simplicity of "one strong base" and the number of files absolutely necessary to deploy will grow.
    And of course, properly running multiple applications and making editor / saving code ready for writing also another code sections, especially library.
  • So I'm a little confused by the last two words above "especially library". Does that mean that your current code does not allow an application to import a library? Or does it mean that it doesn't look for a library code section and if that's the case does that also mean that an existing appjet library wouldn't work?
  • Herby wrote
    Te: "especially library"
    It it still evolving, very basic platform, so it only allows edit applications, no libraries (even if you call them lib-..., name does not matter, even if original Appjet or jgate). What matters id presence of /* appjet:library */ section, and no other sections except /* appjet:server */ is actually saved.
    So it's matter of some more exolution to enhance editor to accept all the rest of code sectiond.
  • Herby wrote
    typo
    it should have been "Re:" in the title :-(
  • Herby wrote
    another typo
    "even if" should have been "even in"

trackback

TrackBack ping me at:
http://blog.herby.sk/blosxom/Programming/AppJet platform clone/phase-3.trackback

comment...

Name:
URL/Email: [http://... or mailto:you@wherever] (optional)
Title: (optional)
Comments:
Checkcode:
CAPTCHA
Save my Name and URL/Email for next time