sysout.js
// Demonstrate calling external programs, by Warren E. Downs (2015)
// Based on examples by Steve Garman
// This program also demonstrates using Asynchronous Callbacks
// to continue processing once the result of the external program
// is received.
 
//Called when application is started.
function OnStart() {
    // Show roots >100 Mb in size.
    showRoots(100,function() { // Called when showRoots is complete
        alert('3. The program continues here');
        runCmd( "ExpectAnErrorHere", function(lines) {
            alert("Expected empty array: "+JSON.stringify(lines));
        }, function(errmsg) {
            alert('Expected Error: '+errmsg);
        });
    });
    alert('1. Immediate continuation.');
 } 
 
// Shows a list of roots greater than specified minMb in size
// Calls next function (if any) once complete.
function showRoots(minMb, next) {
    listRoots(minMb*1000, function(roots) {
        var result="2. Roots:\n";
        for(var xa=0; xa<roots.length; xa++) {
            var freeGb=(roots[xa].available/1024/1024).toFixed(2);
            result += " " + freeGb + " Gb free on "+roots[xa].mountedOn+"\n";
        }
        alert(result);
        if(next) next();
    });
 }
 
//////////////////////////////////////////////////////////////
/**************** Run External Commands *********************/
function runCmd(cmd, gotLines, gotError) {
    var tmp="/sdcard/."+app.GetAppName()+"_runcmd.txt";
    var tmpErr=tmp+".err";
    if(typeof globs === "undefined") { globs={}; }
    if(cmd != null) { // Pull result from previous run if cmd == null
        var outTmp=tmp+".tmp";
        var errTmp=tmpErr+".tmp";
        var redOut=" >"  + outTmp;
        var redErr=" 2>" + errTmp;
        cmd += redOut  + redErr;
        cmd += "; mv " + outTmp + " " + tmp;    // Rename out & err once done.
        cmd += "; mv " + errTmp + " " + tmpErr;
        cmd += "\n";
 
        // On-demand create globs.sys if not already created
        if(globs.sys == null) { globs.sys = app.CreateSysProc("sh"); }
        globs.sys.Out(cmd);
        globs.runCmdGotLines=gotLines;
        globs.runCmdGotError=gotError;
    }
 
    // Retry until both stdout and stderr exist (though they may be empty)
    if(!app.FileExists(tmp) || !app.FileExists(tmpErr)) {
        setTimeout('runCmd(null, globs.runCmdGotLines, globs.runCmdGotError)', 1);
        return;
    }
 
    // Read stdout and stderr
    var lines=app.ReadFile(tmp); // Retrieve command stdout
    if(lines == null) { alert("Unable to read stdout("+cmd+"): "+tmp); return; }
    var errmsg=app.ReadFile(tmpErr); // Retrieve command stderr
    if(errmsg == null) { alert("Unable to read stderr("+cmd+"): "+tmpErr); return; }
 
    // Process stdout
    lines = lines.split("\n");
    app.DeleteFile(tmp);
 
    gotLines(lines);
    if(errmsg.length > 0) { gotError(errmsg); }
}
 
/****************** List Storage Roots **********************/
function listRoots(minSize, gotRoots) {
    runCmd( "busybox df -k", function(lines) {
        // Filesystem 1K-blocks Used Available Use% Mounted on
        // L                  R    R         R    R L
        // ^^^^^^^^^^^^^^^^^^ HEAD ALIGNMENT ^^^^^^^^^^^^^^^^^
        var head=lines[0];
        // Parse the variable length/fixed-width column headers
        // Columns are fixed width but width depends on content.
        // If content exceeds a column (e.g. Filesystem path),
        // the next line will contain the remaining columns.
        lines=lines.slice(1);
        var headOfs={filesystem:0,blocks:head.indexOf('1K-blocks'),
            used:head.indexOf('Used'),available:head.indexOf('Available'),
            usepct:head.indexOf('Use%'),mountedOn:head.indexOf('Mounted on')};
        headOfs.available = headOfs.used + 4;
        headOfs.used = headOfs.blocks + 9;
        for(var xa=0; xa<lines.length; xa++) {
            var item={};
            var line=lines[xa];
            var nextLine = (xa < lines.length-1) ? lines[xa+1] : "";
            var ret=parseDfLine(headOfs, line, item, nextLine);
            lines[xa]=item;
            if(ret == null) { // Skip next line (already processed)
                lines[++xa] = {};
            }
        }
        var roots=[];
        for(var xa=0; xa<lines.length; xa++) {
            var ll=lines[xa];
            if(ll.blocks > minSize) {
                var mo=ll.mountedOn;
                if(mo.indexOf('/dev') == 0) { continue; }
                if(mo.indexOf('/mnt/') == 0) { continue; }
                if(mo.indexOf('/cache') == 0) { continue; }
                if(mo.indexOf('/system') == 0) { continue; }
                if(mo.indexOf('/Android/data/') > -1) { continue; }
                roots.push(ll);
            }
        }
        roots.sort(function(a, b) {
            if(a.available > b.available) { return -1; }
            if(a.available < b.available) { return  1; }
            return 0;
        });
        gotRoots(roots);
    }, function(errmsg) {
        // alert('Error: '+errmsg); // Uncomment if alert desired
    });
}
 
function parseDfLine(headOfs, line, item, nextLine) {
    if(line.indexOf(' ') > -1) {
        if(item.filesystem == null) { item.filesystem=line.substring(0,headOfs.blocks); }
        item.blocks=parseInt(line.substring(headOfs.blocks, headOfs.used));
        item.used=parseInt(line.substring(headOfs.used, headOfs.available));
        item.available=parseInt(line.substring(headOfs.available, headOfs.usepct));
        item.usepct=parseInt(line.substring(headOfs.usepct, headOfs.mountedOn));
        item.mountedOn=line.substring(headOfs.mountedOn);
    }
    else {
        item.filesystem=line;
        parseDfLine(headOfs, nextLine, item, null);
        return null;
    }
    return item;
}
 
//////////////////////////////////////////////////////////