Added PC11 support for loading tapes remotely
This commit is contained in:
parent
aa131d5413
commit
961dce8f78
15 changed files with 993 additions and 441 deletions
|
|
@ -1094,7 +1094,6 @@ Disk.prototype.load = function(sDiskName, sDiskPath, file, fnNotify, controller)
|
|||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* build(buffer, fModified)
|
||||
*
|
||||
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
|
||||
|
|
@ -1217,24 +1216,6 @@ Disk.prototype.doneLoad = function(sURL, sDiskData, nErrorCode)
|
|||
}
|
||||
/*
|
||||
* The most likely source of any exception will be here, where we're parsing the disk data.
|
||||
*
|
||||
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
|
||||
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
|
||||
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
|
||||
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
|
||||
*
|
||||
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
|
||||
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
|
||||
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
|
||||
* the following RegExp replacements take care of those requirements.
|
||||
*
|
||||
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
|
||||
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
|
||||
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
|
||||
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
|
||||
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
|
||||
*
|
||||
* ["unrecognized disk path: test.img"]
|
||||
*/
|
||||
var aDiskData;
|
||||
if (sDiskData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
|
||||
|
|
@ -1249,6 +1230,25 @@ Disk.prototype.doneLoad = function(sURL, sDiskData, nErrorCode)
|
|||
*/
|
||||
aDiskData = ["Missing disk image: " + this.sDiskName];
|
||||
} else {
|
||||
/*
|
||||
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
|
||||
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
|
||||
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
|
||||
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
|
||||
*
|
||||
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
|
||||
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
|
||||
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
|
||||
* the following RegExp replacements take care of those requirements.
|
||||
*
|
||||
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
|
||||
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
|
||||
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
|
||||
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
|
||||
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
|
||||
*
|
||||
* ["unrecognized disk path: test.img"]
|
||||
*/
|
||||
if (sDiskData.indexOf("0x") < 0 && sDiskData.substr(0, 2) != "[\"") {
|
||||
aDiskData = JSON.parse(sDiskData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -659,10 +659,7 @@ FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
bus.addPortOutputTable(this, FDC.aPortOutput);
|
||||
|
||||
this.addDiskette("None", "", true);
|
||||
|
||||
if (this.fLocalDisks) {
|
||||
this.addDiskette("Local Disk", "?");
|
||||
}
|
||||
if (this.fLocalDisks) this.addDiskette("Local Disk", "?");
|
||||
this.addDiskette("Remote Disk", "??");
|
||||
|
||||
if (!this.autoMount()) this.setReady();
|
||||
|
|
@ -1535,6 +1532,7 @@ FDC.prototype.doneLoadDiskette = function onFDCLoadNotify(drive, disk, sDiskette
|
|||
/**
|
||||
* addDiskette(sName, sPath, fTop)
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {string} sName
|
||||
* @param {string} sPath
|
||||
* @param {boolean} [fTop] (default is bottom)
|
||||
|
|
@ -1563,6 +1561,7 @@ FDC.prototype.addDiskette = function(sName, sPath, fTop)
|
|||
* This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
|
||||
* if we can find the path in the "listDisks" control, then we return the associated disk name.
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {string} sPath
|
||||
* @return {string|null}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var State = require("../../shared/lib/state");
|
||||
var PDP11 = require("./defines");
|
||||
|
|
@ -48,14 +49,30 @@ function PC11(parms)
|
|||
{
|
||||
Component.call(this, "PC11", parms, PC11);
|
||||
|
||||
/*
|
||||
* We record any 'autoMount' object now, but we no longer parse it until initBus(), because the Computer's
|
||||
* getMachineParm() service may have an override for us.
|
||||
*/
|
||||
this.configMount = parms['autoMount'] || null;
|
||||
|
||||
/*
|
||||
* TODO: Technically, the PC11 should have a timer that "clocks" data from the abReader buffer into the
|
||||
* PRB register at the appropriate rate (300 CPS for the high-speed version, 10 CPS for the low-speed version).
|
||||
*/
|
||||
this.prs = 0; // PRS register
|
||||
this.prb = 0; // PRB register
|
||||
this.iReader = 0; // buffer index
|
||||
this.abReader = []; // buffer for the PRB register
|
||||
this.prs = 0; // PRS register
|
||||
this.prb = 0; // PRB register
|
||||
this.iReader = 0; // buffer index
|
||||
this.abReader = []; // buffer for the PRB register
|
||||
|
||||
this.flags.local = false;
|
||||
this.sTapeName = this.sTapePath = this.sTapeFile = "";
|
||||
|
||||
/*
|
||||
* Support for local tape images is currently limited to desktop browsers with FileReader support;
|
||||
* when this flag is set, setBinding() allows local tape bindings and informs initBus() to update the
|
||||
* "listTapes" binding accordingly.
|
||||
*/
|
||||
this.fLocalTapes = (!web.isMobile() && window && 'FileReader' in window);
|
||||
}
|
||||
|
||||
Component.subclass(PC11);
|
||||
|
|
@ -64,14 +81,103 @@ Component.subclass(PC11);
|
|||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc)
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer")
|
||||
* @param {string|null} sType is the type of the HTML control (eg, "button", "list", "text", etc)
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listTapes")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
PC11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
||||
{
|
||||
var pc11 = this;
|
||||
|
||||
switch (sBinding) {
|
||||
|
||||
case "listTapes":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onchange = function onChangeListTapes(event) {
|
||||
var controlDesc = pc11.bindings["descTape"];
|
||||
var controlOption = control.options[control.selectedIndex];
|
||||
if (controlDesc && controlOption) {
|
||||
var dataValue = {};
|
||||
var sValue = controlOption.getAttribute("data-value");
|
||||
if (sValue) {
|
||||
try {
|
||||
dataValue = eval("(" + sValue + ")");
|
||||
} catch (e) {
|
||||
Component.error("PC11 option error: " + e.message);
|
||||
}
|
||||
}
|
||||
var sHTML = dataValue['desc'];
|
||||
if (sHTML === undefined) sHTML = "";
|
||||
var sHRef = dataValue['href'];
|
||||
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
|
||||
controlDesc.innerHTML = sHTML;
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "descTape":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadTape":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function onClickLoadTape(event) {
|
||||
var controlTapes = pc11.bindings["listTapes"];
|
||||
if (controlTapes) {
|
||||
var sTapeName = controlTapes.options[controlTapes.selectedIndex].text;
|
||||
var sTapePath = controlTapes.value;
|
||||
pc11.loadSelectedTape(sTapeName, sTapePath);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountTape":
|
||||
if (!this.fLocalTapes) {
|
||||
if (DEBUG) this.log("Local tape support not available");
|
||||
/*
|
||||
* We could also simply hide the control; eg:
|
||||
*
|
||||
* control.style.display = "none";
|
||||
*
|
||||
* but removing the control altogether seems better.
|
||||
*/
|
||||
control.parentNode.removeChild(/** @type {Node} */ (control));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Mount" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = !files.length;
|
||||
});
|
||||
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
if (file) {
|
||||
var sTapePath = file.name;
|
||||
var sTapeName = str.getBaseName(sTapePath, true);
|
||||
pc11.loadSelectedTape(sTapeName, sTapePath, file);
|
||||
}
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
@ -91,9 +197,30 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
this.configMount = this.cmp.getMachineParm('autoMount') || this.configMount;
|
||||
|
||||
if (this.configMount) {
|
||||
if (typeof this.configMount == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
this.configMount = eval("(" + this.configMount + ")");
|
||||
} catch (e) {
|
||||
Component.error("PC11 auto-mount error: " + e.message + " (" + this.configMount + ")");
|
||||
this.configMount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bus.addIOTable(this, PC11.UNIBUS_IOTABLE);
|
||||
|
||||
this.setReady();
|
||||
this.addTape("None", "", true);
|
||||
if (this.fLocalTapes) this.addTape("Local Tape", "?");
|
||||
this.addTape("Remote Tape", "??");
|
||||
|
||||
if (!this.autoMount()) this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -142,6 +269,318 @@ PC11.prototype.reset = function()
|
|||
this.abReader = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* autoMount(fRemount)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {boolean} [fRemount] is true if we're remounting all auto-mounted tapes
|
||||
* @return {boolean} true if one or more tape images are being auto-mounted, false if none
|
||||
*/
|
||||
PC11.prototype.autoMount = function(fRemount)
|
||||
{
|
||||
if (!fRemount) this.cAutoMount = 0;
|
||||
if (this.configMount) {
|
||||
var sTapePath = this.configMount['path'];
|
||||
var sTapeName = this.configMount['name'] || this.findTape(sTapePath);
|
||||
if (sTapePath && sTapeName) {
|
||||
if (!this.loadTape(sTapeName, sTapePath, true) && fRemount) {
|
||||
this.setReady(false);
|
||||
}
|
||||
} else {
|
||||
this.notice("Incorrect auto-mount settings for PC11 (" + JSON.stringify(this.configMount) + ")");
|
||||
}
|
||||
}
|
||||
return !!this.cAutoMount;
|
||||
};
|
||||
|
||||
/**
|
||||
* loadSelectedTape(sTapeName, sTapePath, file)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
*/
|
||||
PC11.prototype.loadSelectedTape = function(sTapeName, sTapePath, file)
|
||||
{
|
||||
if (!sTapePath) {
|
||||
this.unloadTape();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sTapePath == "?") {
|
||||
this.notice('Use "Choose File" and "Mount" to select and load a local tape.');
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the special path of "??" is selected, then we want to prompt the user for a URL. Oh, and
|
||||
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
|
||||
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
|
||||
*
|
||||
* TODO: This is literally all I've done to support remote tape images. There's probably more
|
||||
* I should do, like dynamically updating "listTapes" to include new entries, and adding new entries
|
||||
* to the save/restore data.
|
||||
*/
|
||||
if (sTapePath == "??") {
|
||||
sTapePath = window.prompt("Enter the URL of a remote tape image.", "") || "";
|
||||
if (!sTapePath) return;
|
||||
sTapeName = str.getBaseName(sTapePath);
|
||||
if (DEBUG) this.println("Attempting to load " + sTapePath + " as \"" + sTapeName + "\"");
|
||||
}
|
||||
|
||||
this.loadTape(sTapeName, sTapePath, false, file);
|
||||
};
|
||||
|
||||
/**
|
||||
* loadTape(sTapeName, sTapePath, fAutoMount, file)
|
||||
*
|
||||
* NOTE: If sTapePath is already loaded, nothing needs to be done.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {boolean} [fAutoMount]
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
* @return {number} 1 if tape loaded, 0 if queued up (or busy), -1 if already loaded
|
||||
*/
|
||||
PC11.prototype.loadTape = function(sTapeName, sTapePath, fAutoMount, file)
|
||||
{
|
||||
if (sTapePath) {
|
||||
if (this.sTapePath.toLowerCase() != sTapePath.toLowerCase()) {
|
||||
|
||||
this.unloadTape(fAutoMount, true);
|
||||
|
||||
if (this.flags.busy) {
|
||||
this.notice("PC11 busy");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if (DEBUG) this.println("tape queued: " + sTapeName);
|
||||
|
||||
this.flags.busy = true;
|
||||
if (fAutoMount) {
|
||||
this.fAutoMount = true;
|
||||
this.cAutoMount++;
|
||||
if (this.messageEnabled()) this.printMessage("auto-loading tape: " + sTapeName);
|
||||
}
|
||||
|
||||
this.flags.local = !!file;
|
||||
if (!this.load(sTapeName, sTapePath, file)) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (DEBUG) this.println("tape loaded");
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* load(sTapeName, sTapePath, file)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
* @return {boolean} true if load completed (successfully or not), false if queued
|
||||
*/
|
||||
PC11.prototype.load = function(sTapeName, sTapePath, file)
|
||||
{
|
||||
var sTapeURL = sTapePath;
|
||||
|
||||
if (DEBUG) {
|
||||
var sMessage = 'load("' + sTapeName + '","' + sTapePath + '")';
|
||||
this.printMessage(sMessage);
|
||||
}
|
||||
|
||||
var sTapeFile = str.getBaseName(sTapePath);
|
||||
|
||||
var pc11 = this;
|
||||
|
||||
if (file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
pc11.build(reader.result);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's an occurrence of API_ENDPOINT anywhere in the path, we assume we can use it as-is;
|
||||
* ie, that the user has already formed a URL of the type we use ourselves for unconverted tape images.
|
||||
*/
|
||||
if (sTapePath.indexOf(DumpAPI.ENDPOINT) < 0) {
|
||||
/*
|
||||
* If the selected tape image has a "json" extension, then we assume it's a pre-converted
|
||||
* JSON-encoded tape image, so we load it as-is; otherwise, we ask our server-side tape image
|
||||
* converter to return the corresponding JSON-encoded data.
|
||||
*/
|
||||
var sTapeExt = str.getExtension(sTapePath);
|
||||
if (sTapeExt == DumpAPI.FORMAT.JSON || sTapeExt == DumpAPI.FORMAT.JSON_GZ) {
|
||||
sTapeURL = encodeURI(sTapePath);
|
||||
} else {
|
||||
var sTapeParm = DumpAPI.QUERY.PATH;
|
||||
sTapeURL = web.getHost() + DumpAPI.ENDPOINT + '?' + sTapeParm + '=' + encodeURIComponent(sTapePath) + "&" + DumpAPI.QUERY.FORMAT + "=" + DumpAPI.FORMAT.JSON;
|
||||
}
|
||||
}
|
||||
|
||||
return !!web.getResource(sTapeURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
pc11.doneLoad(sURL, sTapeName, sTapePath, sResponse, nErrorCode);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* build(buffer)
|
||||
*
|
||||
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {?} buffer (we KNOW this is an ArrayBuffer, but we can't seem to convince the Closure Compiler)
|
||||
*/
|
||||
PC11.prototype.build = function(buffer)
|
||||
{
|
||||
if (buffer) {
|
||||
this.aTapeData = new Uint8Array(buffer, 0, buffer.byteLength);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* doneLoad(sURL, sTapeName, sTapePath, sTapeData, nErrorCode)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sURL
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {string} sTapeData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
PC11.prototype.doneLoad = function(sURL, sTapeName, sTapePath, sTapeData, nErrorCode)
|
||||
{
|
||||
this.flags.busy = false;
|
||||
|
||||
var fPrintOnly = (nErrorCode < 0 && this.cmp && !this.cmp.flags.powered);
|
||||
|
||||
if (nErrorCode) {
|
||||
/*
|
||||
* This can happen for innocuous reasons, such as the user switching away too quickly, forcing
|
||||
* the request to be cancelled. And unfortunately, the browser cancels XMLHttpRequest requests
|
||||
* BEFORE it notifies any page event handlers, so if the Computer's being powered down, we won't know
|
||||
* that yet. For now, we rely on the lack of a specific error (nErrorCode < 0), and suppress the
|
||||
* notify() alert if there's no specific error AND the computer is not powered up yet.
|
||||
*/
|
||||
this.notice("Unable to load tape \"" + sTapeName + "\" (error " + nErrorCode + ": " + sURL + ")", fPrintOnly);
|
||||
}
|
||||
else {
|
||||
if (DEBUG && this.messageEnabled()) {
|
||||
this.printMessage('doneLoad("' + sTapePath + '")');
|
||||
}
|
||||
|
||||
Component.addMachineResource(this.idMachine, sURL, sTapeData);
|
||||
|
||||
var resource = web.parseMemoryResource(sURL, sTapeData);
|
||||
if (resource) {
|
||||
this.sTapeName = sTapeName;
|
||||
this.sTapePath = sTapePath;
|
||||
this.aTapeData = resource.aBytes;
|
||||
if (DEBUG) this.println("tape loaded: " + sTapeName);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fAutoMount) {
|
||||
this.fAutoMount = false;
|
||||
if (!--this.cAutoMount) this.setReady();
|
||||
}
|
||||
|
||||
this.displayTape();
|
||||
};
|
||||
|
||||
/**
|
||||
* addTape(sName, sPath, fTop)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sName
|
||||
* @param {string} sPath
|
||||
* @param {boolean} [fTop] (default is bottom)
|
||||
*/
|
||||
PC11.prototype.addTape = function(sName, sPath, fTop)
|
||||
{
|
||||
var controlTapes = this.bindings["listTapes"];
|
||||
if (controlTapes && controlTapes.options) {
|
||||
for (var i = 0; i < controlTapes.options.length; i++) {
|
||||
if (controlTapes.options[i].value == sPath) return;
|
||||
}
|
||||
var controlOption = document.createElement("option");
|
||||
controlOption.text = sName;
|
||||
controlOption.value = sPath;
|
||||
if (fTop && controlTapes.childNodes[0]) {
|
||||
controlTapes.insertBefore(controlOption, controlTapes.childNodes[0]);
|
||||
} else {
|
||||
controlTapes.appendChild(controlOption);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* findTape(sPath)
|
||||
*
|
||||
* This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
|
||||
* if we can find the path in the "listTapes" control, then we return the associated tape name.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sPath
|
||||
* @return {string|null}
|
||||
*/
|
||||
PC11.prototype.findTape = function(sPath)
|
||||
{
|
||||
var controlTapes = this.bindings["listTapes"];
|
||||
if (controlTapes && controlTapes.options) {
|
||||
for (var i = 0; i < controlTapes.options.length; i++) {
|
||||
var control = controlTapes.options[i];
|
||||
if (control.value == sPath) return control.text;
|
||||
}
|
||||
}
|
||||
return str.getBaseName(sPath, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* displayTape()
|
||||
*
|
||||
* @this {PC11}
|
||||
*/
|
||||
PC11.prototype.displayTape = function()
|
||||
{
|
||||
var controlTapes = this.bindings["listTapes"];
|
||||
|
||||
if (controlTapes && controlTapes.options) {
|
||||
var sTargetPath = (this.flags.local? "?" : this.sTapePath);
|
||||
for (var i = 0; i < controlTapes.options.length; i++) {
|
||||
if (controlTapes.options[i].value == sTargetPath) {
|
||||
if (controlTapes.selectedIndex != i) {
|
||||
controlTapes.selectedIndex = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == controlTapes.options.length) controlTapes.selectedIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* unloadTape(fAutoUnload, fQuiet)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {boolean} [fAutoUnload] is true if this unload is being forced as part of an automount and/or restored mount
|
||||
* @param {boolean} [fQuiet]
|
||||
*/
|
||||
PC11.prototype.unloadTape = function(fAutoUnload, fQuiet)
|
||||
{
|
||||
this.sTapeName = "";
|
||||
this.sTapePath = "";
|
||||
this.flags.local = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -298,10 +298,45 @@ web.parseMemoryResource = function(sURL, sData)
|
|||
nLoad: null,
|
||||
nExec: null
|
||||
};
|
||||
|
||||
if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
|
||||
try {
|
||||
var a, ib;
|
||||
var data = eval("(" + sData + ")");
|
||||
var a, ib, data;
|
||||
|
||||
if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
|
||||
/*
|
||||
* Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,
|
||||
* but more recent server configs now display a somewhat friendlier HTML error page. The downside,
|
||||
* however, is that the original error has been buried, and we've received "data" that isn't actually
|
||||
* tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message.
|
||||
*/
|
||||
throw new Error(sData);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
|
||||
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
|
||||
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
|
||||
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
|
||||
*
|
||||
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
|
||||
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
|
||||
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
|
||||
* the following RegExp replacements take care of those requirements.
|
||||
*
|
||||
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
|
||||
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
|
||||
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
|
||||
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
|
||||
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
|
||||
*
|
||||
* ["unrecognized disk path: test.img"]
|
||||
*/
|
||||
if (sData.indexOf("0x") < 0 && sData.substr(0, 2) != "[\"") {
|
||||
data = JSON.parse(sData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
|
||||
} else {
|
||||
data = eval("(" + sData + ")");
|
||||
}
|
||||
|
||||
resource.nLoad = data['load'];
|
||||
resource.nExec = data['exec'];
|
||||
|
|
@ -347,22 +382,26 @@ web.parseMemoryResource = function(sURL, sData)
|
|||
resource = null;
|
||||
}
|
||||
} catch (e) {
|
||||
Component.error("Resource data error: " + e.message);
|
||||
Component.error("Resource data error (" + sURL + "): " + e.message);
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the data manually; we'll assume it's in "simplified" hex form
|
||||
* (a series of hex byte-values separated by whitespace).
|
||||
* Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.
|
||||
*/
|
||||
var ab = [];
|
||||
var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asHexData = sHexData.split(" ");
|
||||
resource.aBytes = new Array(asHexData.length);
|
||||
for (i = 0; i < asHexData.length; i++) {
|
||||
resource.aBytes[i] = parseInt(asHexData[i], 16);
|
||||
Component.assert(!isNaN(resource.aBytes[i]));
|
||||
var n = parseInt(asHexData[i], 16);
|
||||
if (isNaN(n)) {
|
||||
Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")");
|
||||
break;
|
||||
}
|
||||
ab.push(n & 0xff);
|
||||
}
|
||||
if (i == asHexData.length) resource.aBytes = ab;
|
||||
}
|
||||
return resource;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -740,21 +740,32 @@
|
|||
<xsl:template match="device[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/device"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
<xsl:apply-templates select="document($componentFile)/device">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="mount" select="@automount"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="device[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="mount" select="''"/>
|
||||
<xsl:variable name="type">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type"><xsl:value-of select="@type"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="autoMount">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
|
||||
<xsl:when test="@automount"><xsl:value-of select="@automount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@autoMount"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">device</xsl:with-param>
|
||||
<xsl:with-param name="parms">,type:'<xsl:value-of select="$type"/>'</xsl:with-param>
|
||||
<xsl:with-param name="parms">,type:'<xsl:value-of select="$type"/>',autoMount:'<xsl:value-of select="$autoMount"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -880,10 +891,11 @@
|
|||
<xsl:template match="fdc[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="mount" select="''"/>
|
||||
<xsl:variable name="automount">
|
||||
<xsl:variable name="autoMount">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@automount"/></xsl:otherwise>
|
||||
<xsl:when test="@automount"><xsl:value-of select="@automount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@autoMount"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sortBy">
|
||||
|
|
@ -895,7 +907,7 @@
|
|||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">fdc</xsl:with-param>
|
||||
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$automount"/>',sortBy:'<xsl:value-of select="$sortBy"/>'</xsl:with-param>
|
||||
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$autoMount"/>',sortBy:'<xsl:value-of select="$sortBy"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue