Fixes to machine save support

This commit is contained in:
Jeff Parsons 2016-02-17 11:05:27 -08:00
commit 585a851ce5
14 changed files with 2476 additions and 2315 deletions

View file

@ -79,6 +79,7 @@ pcjs:
- /modules/pcjs/lib/state.js
- /modules/pcjs/lib/computer.js
- /modules/shared/lib/embed.js
- /modules/shared/lib/save.js
c1p_scripts:
- /modules/shared/lib/defines.js
- /modules/shared/lib/dumpapi.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -140,7 +140,8 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
Component.call(this, "Computer", parmsComputer, Computer, Messages.COMPUTER);
this.aFlags.fPowered = false;
this.parmsMachine = parmsMachine;
this.setMachineParms(parmsMachine);
this.fAutoPower = this.getMachineParm('autoPower', parmsComputer);
@ -193,7 +194,7 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
*/
var iComponent, component;
var aComponents = Component.getComponents(this.id);
this.panel = Component.getComponentByType("Panel", this.id);
this.panel = /** @type {Panel} */ (Component.getComponentByType("Panel", this.id));
if (this.panel && this.panel.controlPrint) {
for (iComponent = 0; iComponent < aComponents.length; iComponent++) {
@ -337,10 +338,35 @@ Computer.prototype.getMachineID = function()
return this.sMachineID;
};
/**
* setMachineParms(parmsMachine)
*
* If no explicit machine parms were provided, then we check for 'parms' in the bundled resources (if any).
*
* @param {Object} [parmsMachine]
*/
Computer.prototype.setMachineParms = function(parmsMachine)
{
if (!parmsMachine) {
var sParms;
if (typeof resources == 'object' && (sParms = resources['parms'])) {
try {
parmsMachine = /** @type {Object} */ (eval("(" + sParms + ")"));
} catch(e) {
Component.error(e.message + " (" + sParms + ")");
}
}
}
this.parmsMachine = parmsMachine;
};
/**
* getMachineParm(sParm, parmsComponent)
*
* If the machine parameter doesn't exist, and component parameters have been provided, we'll automatically look up the latter.
* If the machine parameter doesn't exist, we check for a matching component parameter (if parmsComponent is provided),
* and failing that, we check the bundled resources (if any).
*
* At the moment, the only bundled resource match we expect to encounter is 'state'.
*
* @param {string} sParm
* @param {Object} [parmsComponent]
@ -348,12 +374,27 @@ Computer.prototype.getMachineID = function()
*/
Computer.prototype.getMachineParm = function(sParm, parmsComponent)
{
var value = null;
if (this.parmsMachine) {
if (this.parmsMachine[sParm] != null) {
return this.parmsMachine[sParm];
value = this.parmsMachine[sParm];
}
if (value == null && parmsComponent) {
value = parmsComponent[sParm];
}
return parmsComponent? parmsComponent[sParm] : null;
if (value == null && typeof resources == 'object') {
value = resources[sParm];
}
return value;
};
/**
* saveMachineParms()
*
* @return {string|null}
*/
Computer.prototype.saveMachineParms = function()
{
return this.parmsMachine? JSON.stringify(this.parmsMachine) : null;
};
/**
@ -1425,7 +1466,7 @@ Computer.show = function()
for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) {
var eComputer = aeComputers[iComputer];
var parmsComputer = Component.getComponentParms(eComputer);
var computer = Component.getComponentByType("Computer", parmsComputer['id']);
var computer = /** @type {Computer} */ (Component.getComponentByType("Computer", parmsComputer['id']));
if (computer) {
if (DEBUG && computer.messageEnabled()) {
@ -1474,7 +1515,7 @@ Computer.exit = function()
for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) {
var eComputer = aeComputers[iComputer];
var parmsComputer = Component.getComponentParms(eComputer);
var computer = Component.getComponentByType("Computer", parmsComputer['id']);
var computer = /** @type {Computer} */ (Component.getComponentByType("Computer", parmsComputer['id']));
if (computer) {
if (DEBUG && computer.messageEnabled()) {

View file

@ -6366,23 +6366,23 @@ if (DEBUGGER) {
}
if (sAddr == "state") {
var s = this.cmp.powerOff(true);
var sState = this.cmp.powerOff(true);
if (sLen == "console") {
/*
* Console buffers are notoriously small, and even the following code, which breaks the
* data into parts (eg, "d state console 1", "d state console 2", etc) just isn't that helpful.
*
* var nPart = +sBytes;
* if (nPart) s = s.substr(1000000 * (nPart-1), 1000000);
* if (nPart) sState = sState.substr(1000000 * (nPart-1), 1000000);
*
* So, the best way to capture a large machine state is to run your own local server and use
* server-side storage. Take a look at the "Save" binding in computer.js, which binds an HTML
* control to the computer.powerOff() and computer.saveServerState() functions.
*/
console.log(s);
console.log(sState);
} else {
this.doClear();
this.println(s);
this.println(sState);
}
return;
}

View file

@ -495,8 +495,8 @@ Component.getComponentByType = function(sType, idRelated, componentPrev)
*/
Component.getComponentParms = function(element)
{
var parms = null,
sParms = element.getAttribute("data-value");
var parms = null;
var sParms = element.getAttribute("data-value");
if (sParms) {
try {
parms = eval("(" + sParms + ")"); // jshint ignore:line

View file

@ -32,7 +32,7 @@
"use strict";
/* global document: true, window: true, XSLTProcessor: false, APPNAME: false, APPVERSION: false, DEBUG: true */
/* global document: true, window: true, XSLTProcessor: false, APPNAME: false, APPVERSION: false, XMLVERSION: true, DEBUG: true */
if (NODE) {
var Component = require("./component");
@ -500,110 +500,12 @@ function embedPC(idMachine, sXMLFile, sXSLFile, sParms)
return embedMachine("PCjs", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
}
/**
* savePC(idMachine, sPCJSFile)
*
* @param {string} idMachine
* @param {string} [sPCJSFile]
* @return {boolean} true if successful, false if error
*/
function savePC(idMachine, sPCJSFile)
{
var cmp = Component.getComponentByType("Computer", idMachine);
var dbg = Component.getComponentByType("Debugger", idMachine);
if (cmp) {
if (!sPCJSFile) sPCJSFile = "/versions/pcjs/" + (XMLVERSION || APPVERSION) + "/pc" + (dbg? "-dbg" : "") + ".js";
web.loadResource(sPCJSFile, true, null, null, downloadPC, [idMachine, cmp, dbg]);
return true;
}
web.alertUser("Unable to identify machine '" + idMachine + "'");
return false;
}
/**
* downloadPC(sURL, sPCJS, nErrorCode, aMachineInfo)
*
* @param {string} sURL
* @param {string} sPCJS
* @param {number} nErrorCode
* @param {string} aMachineInfo ([0] = idMachine, [1] = Computer component, [2] = Debugger component, if any)
*/
function downloadPC(sURL, sPCJS, nErrorCode, aMachineInfo)
{
/*
* sPCJS is supposed to contain the entire PCjs script, which has been wrapped with:
*
* (function(){...
*
* at the top and:
*
* ...})();
*
* at the bottom, thanks to the following Closure Compiler option:
*
* --output_wrapper "(function(){%output%})();"
*
* Immediately inside that wrapping, we want to embed all the specified machine's resources, using:
*
* var resources = {"xml": "...", "xsl": "...", ...};
*
* Note that the "resources" variable has been added to our externs.js, to prevent it from being renamed
* by the Closure Compiler.
*/
if (sPCJS) {
var idMachine = aMachineInfo[0];
var matchScript = sPCJS.match(/^(\s*\(function\(\)\{)([\s\S]*)(}\)\(\);\s*)$/);
if (matchScript) {
var resources = Component.getMachineResources(idMachine), resourcesNew = {};
for (var name in resources) {
var data = resources[name];
if (name == "xml") {
/*
* Look through this resource for <disk> entries whose paths do not appear as one of the other
* machine resources, and remove those entries.
*/
var matchDisk, reDisk = /[ \t]*<disk [^>]*path=(['"])(.*?)\1.*?<\/disk>\n?/g;
while (matchDisk = reDisk.exec(resources[name])) {
var path = matchDisk[2];
if (path) {
if (resources[path]) {
console.log("saving disk: '" + path);
} else {
data = data.replace(matchDisk[0], "");
}
}
}
}
console.log("saving resource: '" + name + "' (" + data.length + " bytes)");
resourcesNew[name] = data;
}
var sResources = JSON.stringify(resourcesNew);
sPCJS = matchScript[1] + "var resources=" + sResources + ";" + matchScript[2] + matchScript[3];
console.log("saving machine: '" + idMachine + "' (" + sPCJS.length + " bytes)");
var uri = "data:application/octet-stream;base64," + btoa(sPCJS);
var link = document.createElement('a');
if (typeof link.download == 'string') {
link.href = uri;
link.download = str.getBaseName(sURL, true) + ".json";
document.body.appendChild(link); // Firefox requires the link to be in the body (?)
link.click();
document.body.removeChild(link);
} else {
window.open(uri);
}
}
}
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them
* as (named) properties of a global object.
*/
if (APPNAME == "PCjs") {
window['embedPC'] = embedPC;
window['savePC'] = savePC;
}
if (APPNAME == "C1Pjs") {

206
modules/shared/lib/save.js Normal file
View file

@ -0,0 +1,206 @@
/**
* @fileoverview PCjs save functionality.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Feb-17
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/* global window: true, APPVERSION: false, XMLVERSION: true, DEBUG: true */
if (NODE) {
var Component = require("./component");
var str = require("./strlib");
var web = require("./weblib");
}
/**
* savePC(idMachine, sPCJSFile)
*
* @param {string} idMachine
* @param {string} [sPCJSFile]
* @return {boolean} true if successful, false if error
*/
function savePC(idMachine, sPCJSFile)
{
var cmp = /** @type {Computer} */ (Component.getComponentByType("Computer", idMachine));
var dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", idMachine));
if (cmp) {
var sState = cmp.powerOff(true);
var sParms = cmp.saveMachineParms();
if (!sPCJSFile) {
if (MAXDEBUG) {
sPCJSFile = "/tmp/pcjs/" + (XMLVERSION || APPVERSION) + "/pc.js"
} else {
sPCJSFile = "/versions/pcjs/" + (XMLVERSION || APPVERSION) + "/pc" + (dbg? "-dbg" : "") + ".js";
}
}
web.loadResource(sPCJSFile, true, null, null, downloadPC, [idMachine, sParms, sState, cmp, dbg]);
return true;
}
web.alertUser("Unable to identify machine '" + idMachine + "'");
return false;
}
/**
* downloadPC(sURL, sPCJS, nErrorCode, aMachineInfo)
*
* @param {string} sURL
* @param {string} sPCJS
* @param {number} nErrorCode
* @param {string} aMachineInfo ([0] = idMachine, [1] = sParms, [2] = sState, [3] = Computer component, [4] = Debugger component, if any)
*/
function downloadPC(sURL, sPCJS, nErrorCode, aMachineInfo)
{
var matchScript;
var idMachine = aMachineInfo[0], sXMLFile, sXSLFile;
/*
* sPCJS is supposed to contain the entire PCjs script, which has been wrapped with:
*
* (function(){...
*
* at the top and:
*
* ...})();
*
* at the bottom, thanks to the following Closure Compiler option:
*
* --output_wrapper "(function(){%output%})();"
*
* Immediately inside that wrapping, we want to embed all the specified machine's resources, using:
*
* var resources = {"xml": "...", "xsl": "...", ...};
*
* Note that the "resources" variable has been added to our externs.js, to prevent it from being renamed
* by the Closure Compiler.
*/
if (sPCJS) {
matchScript = sPCJS.match(/^(\s*\(function\(\)\{)([\s\S]*)(}\)\(\);\s*)$/);
if (!matchScript) {
/*
* If the match failed, we assume that a DEBUG (uncompiled) script is being used,
* so we'll provide a fake match that should work with whatever script was provided.
*/
if (DEBUG) {
matchScript = [sPCJS, "", sPCJS, ""];
} else {
sPCJS = "";
}
}
}
if (sPCJS) {
var resOld = Component.getMachineResources(idMachine), resNew = {};
for (var sName in resOld) {
var data = resOld[sName];
var sExt = str.getExtension(sName);
if (sExt == "xml") {
/*
* Look through this resource for <disk> entries whose paths do not appear as one of the
* other machine resources, and remove those entries.
*/
var matchDisk, reDisk = /[ \t]*<disk [^>]*path=(['"])(.*?)\1.*?<\/disk>\n?/g;
while (matchDisk = reDisk.exec(resOld[sName])) {
var path = matchDisk[2];
if (path) {
if (resOld[path]) {
Component.log("recording disk: '" + path + "'");
} else {
data = data.replace(matchDisk[0], "");
}
}
}
sXMLFile = sName = str.getBaseName(sName);
}
else if (sExt == "xsl") {
sXSLFile = sName = str.getBaseName(sName);
}
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
resNew[sName] = data;
}
if (aMachineInfo[1]) {
var sParms = resNew[sName = 'parms'] = aMachineInfo[1];
Component.log("saving resource: '" + sName + "' (" + sParms.length + " bytes)");
}
if (aMachineInfo[2]) {
var sState = resNew[sName = 'state'] = aMachineInfo[2];
Component.log("saving resource: '" + sName + "' (" + sState.length + " bytes)");
}
if (sXMLFile && sXSLFile) {
var uri;
var sResources = JSON.stringify(resNew);
var sScriptFile = str.getBaseName(sURL, true);
sPCJS = matchScript[1] + "var resources=" + sResources + ";" + matchScript[2] + matchScript[3];
Component.log("saving machine: '" + idMachine + "' (" + sPCJS.length + " bytes)");
if (MAXDEBUG) {
sPCJS = sPCJS.replace(/[\u00A0-\u2666]/g, function(c) {
return '&#' + c.charCodeAt(0) + ';';
});
uri = "data:application/javascript;base64," + btoa(sPCJS);
} else {
uri = "data:application/javascript," + encodeURI(sPCJS);
}
var link = document.createElement('a');
if (typeof link.download == 'string') {
link.href = uri;
link.download = sScriptFile + ".json";
document.body.appendChild(link); // Firefox requires the link to be in the body (?)
link.click();
document.body.removeChild(link);
var sAlert = 'Check your Downloads folder for "' + sScriptFile + '.json", ';
sAlert += 'copy it to your web server as "' + sScriptFile + '.js", and then add the following to your web page:\n\n';
sAlert += '<div id="' + idMachine + '"></div>\n';
sAlert += '...\n';
sAlert += '<script type="text/javascript" src="' + sScriptFile + '.js"></script>\n';
sAlert += '<script type="text/javascript">embedPC("' + idMachine + '","' + sXMLFile + '","' + sXSLFile + '");</script>\n\n';
sAlert += 'The machine should appear where the <div> is located.';
web.alertUser(sAlert);
} else {
window.open(uri);
}
} else {
web.alertUser("Missing XML/XSL resources");
}
} else {
web.alertUser("Unabled to download script");
}
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them
* as (named) properties of a global object.
*/
window['savePC'] = savePC;

View file

@ -193,7 +193,7 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
var nErrorCode = 0;
var sURLData = null;
if (resources && (sURLData = resources[sURL])) {
if (typeof resources == 'object' && (sURLData = resources[sURL])) {
if (fnNotify) {
if (!componentNotify) {
fnNotify(sURL, sURLData, nErrorCode, pNotify);

View file

@ -132,6 +132,7 @@
"./modules/pcjs/lib/debugger.js",
"./modules/pcjs/lib/state.js",
"./modules/pcjs/lib/computer.js",
"./modules/shared/lib/embed.js"
"./modules/shared/lib/embed.js",
"./modules/shared/lib/save.js"
]
}

View file

@ -1,5 +1,5 @@
(function(){var f;function n(a,b){var c="";void 0===b?b=8:8<b&&(b=8);if(null==a||isNaN(a))for(;0<b--;)c="?"+c;else for(;0<b--;){var d=a&15,d=d+(0<=d&&9>=d?48:55),c=String.fromCharCode(d)+c;a>>=4}return c}function p(a){return"0x"+n(a,2)}function q(a){return"0x"+n(a,4)}var aa={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"};function ba(a){return a.replace(/[&<>"']/g,function(a){return aa[a]})}var ca=Date.now||function(){return+new Date};
function t(a,b,c){var d=0,e=null;if(resources&&(e=resources[a]))c&&(b?c.call(b,a,e,d,void 0):c(a,e,d,void 0));else{var g=window.XMLHttpRequest?new window.XMLHttpRequest:new window.ActiveXObject("Microsoft.XMLHTTP");g.onreadystatechange=function(){4===g.readyState&&(e=g.responseText,200==g.status||!g.status&&e.length&&"file:"==(window?window.location.protocol:"file:")||(d=g.status||-1),c&&(b?c.call(b,a,e,d,void 0):c(a,e,d,void 0)))};g.open("GET",a,!0);g.send()}}
function t(a,b,c){var d=0,e=null;if("object"==typeof resources&&(e=resources[a]))c&&(b?c.call(b,a,e,d,void 0):c(a,e,d,void 0));else{var g=window.XMLHttpRequest?new window.XMLHttpRequest:new window.ActiveXObject("Microsoft.XMLHTTP");g.onreadystatechange=function(){4===g.readyState&&(e=g.responseText,200==g.status||!g.status&&e.length&&"file:"==(window?window.location.protocol:"file:")||(d=g.status||-1),c&&(b?c.call(b,a,e,d,void 0):c(a,e,d,void 0)))};g.open("GET",a,!0);g.send()}}
function u(a){window&&window.alert(a)}function v(a){if(window){var b=window?window.navigator.userAgent:"";return"iOS"==a&&b.match(/(iPod|iPhone|iPad)/)&&b.match(/AppleWebKit/)||"MSIE"==a&&b.match(/(MSIE|Trident)/)||0<=b.indexOf(a)?!0:!1}return!1}function da(a,b,c){function d(){--a;0<=a&&(b()||(a=0));0<a?setTimeout(d,0):c()}d()}
function ea(a,b){function c(){b(100===d)&&(e=setTimeout(c,d),d=100)}var d=0,e=null,g=!1;a.onmousedown=function(){g||e||(d=500,c())};a.ontouchstart=function(){e||(d=500,c())};a.onmouseup=a.onmouseout=function(){e&&(clearTimeout(e),e=null)};a.ontouchend=a.ontouchcancel=function(){e&&(clearTimeout(e),e=null);g=!0}}var w={init:[],show:[],exit:[]},fa=!1,ga=!0;function ha(a,b){if(window){var c=window[a];window[a]="function"!==typeof c?b:function(){c&&c();b()}}}function x(a){w.init.push(a)}
function ia(a){if(ga)try{for(var b=0;b<a.length;b++)a[b]()}catch(c){u("An unexpected exception occurred:\n\n"+c.message+"\n\nPlease send this information to support@pcjs.org. Thanks.")}}function y(a){!ga&&a?(ga=!0,fa&&ja("init")):ga=a}function ja(a){w[a]&&ia(w[a])}ha("onload",function(){fa=!0;ia(w.init)});ha("onpageshow",function(){ia(w.show)});ha(v("Opera")||v("iOS")?"onunload":"onbeforeunload",function(){ia(w.exit)});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long