Consolidated core

- Posted in Tech by - Permalink

Finally I have made a version which is consolidated to my liking. The need to setup the domain at two places was solved by putting a default domain in the storage and slightly modifying boot.js:

// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

(function() {
  var _an = appjet._native;
  var tableid = _an.storage_get("obj-root", "bootTable").value;
  var appBoot = _an.storage_get(tableid, request.headers.Host).value;
  var name, queue = appjet._internal.queue = appBoot ? appBoot.split(",") : ["0", "", "lib-0"];
  appjet.appName = queue.shift();
  appjet.mainDomain = queue.shift() || _an.storage_get("obj-root", "defaultDomain").value;
  while((queue = appjet._internal.queue) && (name = queue.shift())) {
    var agent = import({}, name);
    if (typeof agent.runAgent === "function") { agent.runAgent(queue); }
  }
})();

This change is also incorporated into the configure.js.

Then, there is bigger change - the generated storage ids are finally out. The code of lib-admin is now completely free of using appjet._native methods and uses only the standard storage library. This also allowed to make code slightly shorter and to reduce number of "app–storage api" methods from five to four. The filestoring script had not been changed; here is the new configure.js:

// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

// ====== variables to configure ======
var pwd = "undefined";
var domain = "appdomain.net";
// please configure the path to the save script in saveRawSource below if needed
// ====== verbatim copy of lib-admin functions ====== begin ======
function appData(app) {
    var result = storage.apps[app];
    if (result) {
        return result;
    }
    printp(request.path, ": unknown app: ", app);
    response.stop();
}

function appDataNew(app) {
    var result = storage.apps[app];
    if (result) {
        return null;
    }
    _apps[app] = {};
    return _apps[app];
}

function saveRawSource(files) {
    var info = {pwd:storage.pwd};
    for (var p in files) {
        info[p + ".js"] = files[p];
    }
    wpost("http://staticdomain.net/fs.rb", info);
}

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

function get_main() {
    print(OL(LI(link("/rescue")), LI(link("/edit")), LI(link("/create"))));
}

function get_rescue() {
    var app = request.params.app;
    var appdata = appData(app);
    var rescue = appdata["server.bak"];
    saveAppSource(app, rescue);
    printp("Last known good code of ", app, " restored.", BR(), CODE(rescue));
    printp(link("http://admin." + appjet.mainDomain + "/edit?app=" + app, "Go edit it!"));
}

function get_edit() {
    var app = request.params.app;
    var appdata = appData(app);
    page.head.write("<script>\n" + "    var c=" + {s:appdata.server} + ".s;\n" + "    window.onload=function(){var ta=document.getElementById('code');ta.defaultValue=ta.value=c;};\n" + "<" + "/script>");
    page.setTitle(app + html(" &ndash; ") + "admin editor");
    printp(B(app, ".", appjet.mainDomain), BR(), B({style:"color:#0c0"}, "/* appjet:server */"), BR(), FORM({method:"post", action:request.path}, TEXTAREA({id:"code", name:"code", style:"width:100%;height:80ex"}), INPUT({type:"hidden", name:"app", value:app}), INPUT({type:"submit"}), " ", INPUT({type:"reset", value:"Undo"})));
}

function post_edit() {
    var app = request.params.app;
    var appdata = appData(app);
    var code = request.params.code;
    if (code) {
        saveAppSource(app, code, true);
    }
    response.redirect(request.path + "?app=" + encodeURIComponent(app));
}

function get_create() {
    page.setTitle("admin app creation");
    print(FORM({method:"post", action:request.path}, "Supply application name:", INPUT({name:"app"}), INPUT({type:"submit"})));
}

function post_create() {
    var app = request.params.app;
    if (!app.match(/[a-z][a-z0-9-]+[a-z0-9]/)) {
        printp("Application name must", UL(LI("be at least three characters long,"), LI("begin with smallcaps letter,"), LI("end with smallcaps letter or digit,"), LI("and contain only smallcaps letters, digits, or minus sign/dash (-) characters.")));
        get_create();
        return;
    }
    var appdata = appDataNew(app);
    if (appdata === null) {
        printp("Application ", B(CODE(app)), " already exists.");
        get_create();
        return;
    }
    appdata["server.bak"] = "printp(\"Hello, world!\");";
    response.redirect("http://admin." + appjet.mainDomain + "/rescue?app=" + app);
}
// ====== verbatim copy of lib-admin functions ====== end ======

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

"""
];

        [appData, appDataNew, saveRawSource, saveAppSource, get_main, get_rescue, get_edit, post_edit, get_create, post_create, ].
        forEach(function(f) { adminBuilder.push(f); });

        adminBuilder.push("""

import("storage");
appjet._internal.global = this; //dispatch fix
dispatch();""");

        // ====== Storage dump, edited ====== begin ======
       (function () {
       var _an = appjet._native;
       _an.storage_create("obj-MFc9TBCDz");
       _an.storage_create("obj-MFc9TB7MD");
       _an.storage_create("obj-M1OtYQoF");
       _an.storage_create("obj-MFc7dzAMR");
       _an.storage_create("obj-MFc9TB2DK");
       _an.storage_put("obj-MFc9TBCDz", "server.bak", "string", adminBuilder.join(""));
       _an.storage_put("obj-MFc9TB7MD", "server.bak", "string", "printp(\"Hello, world!\");");
       _an.storage_put("obj-root", "pwd", "string", pwd);
       _an.storage_put("obj-root", "apps", "object", "obj-MFc7dzAMR");
       _an.storage_put("obj-root", "bootTable", "object", "obj-M1OtYQoF");
       _an.storage_put("obj-root", "defaultDomain", "string", domain);
       _an.storage_put("obj-M1OtYQoF", "admin." + domain, "string", "admin,,lib-admin");
       _an.storage_put("obj-MFc7dzAMR", "0", "object", "obj-MFc9TB2DK");
       _an.storage_put("obj-MFc7dzAMR", "admin", "object", "obj-MFc9TBCDz");
       _an.storage_put("obj-MFc7dzAMR", "worker", "object", "obj-MFc9TB7MD");
       _an.storage_put("obj-MFc9TB2DK", "server.bak", "string", "// (c) 2009, Herbert Voj\u010d\xedk\n// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)\n\nif (request.headers.Host === \"admin.\"+appjet.mainDomain) {\n    appjet.appName = \"admin\";\n    appjet._internal.queue.unshift(\"lib-admin\");\n} else {\n    import(\"lib-worker\");\n}");
       })();
        // ====== Storage dump, edited ====== end ======

        printp("Storage configured. Backup version of admin, 0 and worker apps included; you may restore them afterwards using admin."+domain+"/rescue.");
        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 lib-admin.js.");
        break;

case '/2':
        import("storage");
        request.params.app="admin";
        get_rescue();
        printp("admin.js restored.");
        printp("You may stop running configure.js. ",
        "Start appjet with boot.js as the main file.");
        break;

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

Things for the future are: first, change the filesaving script so it understands the "empty file means delete" protocol. After that, I can announce the Solid Base, as mentioned in previous post.

Then, it is awkward that all the app-related manipulation is done only in interactive lib-admin. It should be done in form of an API of some sort, which lib-admin can use, but also other apps can use (with some sort of pluggable authorization). But for now, I am not going to pursue this goal directly, I leave this thing to evolve naturally, while having the neccessity of it in mind. After some time, when API crystallizes, the Stable Base 2 can be made from an api library and its interactive shell (like lib-admin is now).

On top of this core, the true multiapp platform should be built - that means (pluggable?) AAA stack, virtualization of storage and cron jobs, making appjet conventions work (code sections, working links in footer etc.), better editor and some version control / team work support. Here, there is still lot of work beyond the implementation itself - I'd like to have the system nicely modular with clearly split responsibilities, and AAA/virtualization/API are somewhat overlapping. I hope I make the right decisions.

As for the source code management, I have the idea of using external repositories which users can set up anywhere on the web, and communicating with them via HTTP (in fact, only getting the file(s)). This way, one can use his own stack of editors/repositories, obnly concern is to make the latest sourcecode available under a specified (non-changing) URL. I must investigate more into it, but it seems there are some repositories than allow this kind of hosting.

And if I am to make my own editor, I think in the way of Smalltalk browser (or Java Browsing perspective of Eclipse, which was directly inspired by the former). So that I can edit more apps at once, and having only one section occupying the editor at a time.