mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-13 11:44:33 +00:00
1. Implement new method to open pop-up windows that schedules pop-up opens through a queue, delaying between opens, and retrying with longer delays when the opens fail. This is to satisfy new restrictions on pop-ups introduced in Apple Safari 11.0, even when the browser is configured to enable pop-ups. 2. Remove the Application Cache facility. This API has been deprecated and will be removed from browsers. 3. Implement improved setCallback() asynchronous timing mechanism with a new Promise-based approach developed for the retro-220 emulator. 4. Correct Processor memory access logic to avoid causing an Invalid Address interrupt in Control State. 5. Correct Processor Exit Character Mode (XX00) by implementing it separately from Word Mode exits. 6. Implement separate jump mechanisms for Word and Character Mode to correct edge-case errors. 7. Make slight correction to clock counting in singlePrecisionAdd. 8. Fix bug in double-clicking the "stacker" of the card punch to extract its contents. 9. Disable I/O descriptor word count in IOUnit for card reader/punch operations; remove extraneous blank card between card-load programs from the COLDSTART-XIII.card and COOLSTART-XIII.card decks that was previously required to prevent "eating" the second program's boot card. 10. Remove extraneous whitespace from B5500FramePaper used for most text-only windows and frames. 11. Compute HPT disk latency based on current timestamp rather than a random number. 12. Correct top-of-form handling in B5500LinePrinter. 13. Correct annimation of tape reel in B5500MagTapeDrive. 14. Replace the emulator's custom bindMethod() utility routine with Function.bind(). 15. Replace the emulator's custom DOM className utility routines by DOM classList methods. 16. Add P1 S and F register values to the internal tape dump caption; improve the dump tape's label records. 17. Give the annunciators on the ConsolePanel a dark gray presence when they are not lit. 18. Correct the method to "focus" the ConsolePanel window after the SPO initializes and becomes ready. 19. Clean up line delimiters in source and make consistent for Windows systems.
808 lines
31 KiB
HTML
808 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<head>
|
|
<title>B5500 Disk File List Utility</title>
|
|
<meta name="Author" content="Paul Kimpel">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
|
<meta http-equiv="Content-Style-Type" content="text/css">
|
|
|
|
<script>
|
|
/***********************************************************************
|
|
* retro-b5500/tools B5500DiskFileList.html
|
|
************************************************************************
|
|
* Copyright (c) 2013, Paul Kimpel.
|
|
* Licensed under the MIT License,
|
|
* see http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* B5500 Disk File List Utility.
|
|
*
|
|
* This script opens an IndexedDB database in the browser and attempts to
|
|
* treat it as a B5500 disk image, listing all of the files and their attributes
|
|
* in the disk directory, and allowing you to select a specific file and list
|
|
* it to a sub-window.
|
|
************************************************************************
|
|
* 2013-04-17 P.Kimpel
|
|
* Original version, from B5500DiskDirList.html.
|
|
* 2015-04-17 P.Kimpel
|
|
* Add "db=" URL parameter.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
if (!window.indexedDB) { // for Safari, mostly
|
|
window.indexedDB = window.webkitIndexedDB || window.mozIndexedDB;
|
|
}
|
|
|
|
window.addEventListener("load", function() {
|
|
var configName = "CONFIG"; // database configuration store name
|
|
var dbName = "B5500DiskUnit"; // IDB database name
|
|
var directoryTop; // start of directory area
|
|
var directoryEnd; // end of directory area
|
|
var euPrefix = "EU"; // prefix for EU object store names
|
|
|
|
var headers = {};
|
|
|
|
var config = null; // copy of CONFIG store contents
|
|
var disk = null; // the IDB database object
|
|
var panel = $$("TextPanel");
|
|
|
|
var BICtoANSIChar = [ // Index by 6-bit BIC to get ANSI character
|
|
"0", "1", "2", "3", "4", "5", "6", "7",
|
|
"8", "9", "#", "@", "?", ":", ">", "}",
|
|
"+", "A", "B", "C", "D", "E", "F", "G",
|
|
"H", "I", ".", "[", "&", "(", "<", "~",
|
|
"|", "J", "K", "L", "M", "N", "O", "P",
|
|
"Q", "R", "$", "*", "-", ")", ";", "{",
|
|
" ", "/", "S", "T", "U", "V", "W", "X",
|
|
"Y", "Z", ",", "%", "!", "=", "]", "\""];
|
|
|
|
var BICtoANSI = [ // Index by 6-bit BIC to get 8-bit ANSI code
|
|
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
|
0x38,0x39,0x23,0x40,0x3F,0x3A,0x3E,0x7D, // 08-1F, @10-17
|
|
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 10-17, @20-27
|
|
0x48,0x49,0x2E,0x5B,0x26,0x28,0x3C,0x7E, // 18-1F, @30-37
|
|
0x7C,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
|
0x51,0x52,0x24,0x2A,0x2D,0x29,0x3B,0x7B, // 28-2F, @50-57
|
|
0x20,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 30-37, @60-67
|
|
0x59,0x5A,0x2C,0x25,0x21,0x3D,0x5D,0x22]; // 38-3F, @70-77
|
|
|
|
var BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-as-ANSI code
|
|
0x23,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
|
0x38,0x39,0x40,0x3F,0x30,0x3A,0x3E,0x7D, // 08-1F, @10-17
|
|
0x2C,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 10-17, @20-27
|
|
0x59,0x5A,0x25,0x21,0x20,0x3D,0x5D,0x22, // 18-1F, @30-37
|
|
0x24,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
|
0x51,0x52,0x2A,0x2D,0x7C,0x29,0x3B,0x7B, // 28-2F, @50-57
|
|
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 30-37, @60-67
|
|
0x48,0x49,0x5B,0x26,0x2E,0x28,0x3C,0x7E]; // 38-3F, @70-77
|
|
|
|
var ANSItoBIC = [ // Index by 8-bit ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
|
0x30,0x3C,0x3F,0x0A,0x2A,0x3B,0x1C,0x0C,0x1D,0x2D,0x2B,0x10,0x3A,0x2C,0x1A,0x31, // 20-2F
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0D,0x2E,0x1E,0x3D,0x0E,0x0C, // 30-3F
|
|
0x0B,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x21,0x22,0x23,0x24,0x25,0x26, // 40-4F
|
|
0x27,0x28,0x29,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x1B,0x0C,0x3E,0x0C,0x0C, // 50-5F
|
|
0x0C,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x21,0x22,0x23,0x24,0x25,0x26, // 60-6F
|
|
0x27,0x28,0x29,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x2F,0x20,0x0F,0x1F,0x0C, // 70-7F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 80-8F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 90-9F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // A0-AF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // B0-BF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // C0-CF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
|
|
|
var BCLANSItoBIC = [ // Index by 8-bit BCL-as-ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
|
0x1C,0x1B,0x1F,0x00,0x20,0x1A,0x3B,0x0C,0x3D,0x2D,0x2A,0x30,0x10,0x2B,0x3C,0x11, // 20-2F
|
|
0x0C,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0D,0x2E,0x3E,0x1D,0x0E,0x0B, // 30-3F
|
|
0x0A,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x21,0x22,0x23,0x24,0x25,0x26, // 40-4F
|
|
0x27,0x28,0x29,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x3A,0x0C,0x1E,0x0C,0x0C, // 50-5F
|
|
0x0C,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x21,0x22,0x23,0x24,0x25,0x26, // 60-6F
|
|
0x27,0x28,0x29,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x2F,0x2C,0x0F,0x3F,0x0C, // 70-7F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 80-8F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 90-9F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // A0-AF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // B0-BF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // C0-CF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
|
|
|
var pow2 = [ // powers of 2 from 0 to 52
|
|
0x1, 0x2, 0x4, 0x8,
|
|
0x10, 0x20, 0x40, 0x80,
|
|
0x100, 0x200, 0x400, 0x800,
|
|
0x1000, 0x2000, 0x4000, 0x8000,
|
|
0x10000, 0x20000, 0x40000, 0x80000,
|
|
0x100000, 0x200000, 0x400000, 0x800000,
|
|
0x1000000, 0x2000000, 0x4000000, 0x8000000,
|
|
0x10000000, 0x20000000, 0x40000000, 0x80000000,
|
|
0x100000000, 0x200000000, 0x400000000, 0x800000000,
|
|
0x1000000000, 0x2000000000, 0x4000000000, 0x8000000000,
|
|
0x10000000000, 0x20000000000, 0x40000000000, 0x80000000000,
|
|
0x100000000000, 0x200000000000, 0x400000000000, 0x800000000000,
|
|
0x1000000000000, 0x2000000000000, 0x4000000000000, 0x8000000000000,
|
|
0x10000000000000];
|
|
|
|
/**************************************/
|
|
function $$(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
/**************************************/
|
|
function bitTest(word, bit) {
|
|
/* Extracts and returns the specified bit from the word */
|
|
var e = 47-bit; // word lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
if (e > 0) {
|
|
return ((word - word % (p = pow2[e]))/p) % 2;
|
|
} else {
|
|
return word % 2;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fieldIsolate(word, start, width) {
|
|
/* Extracts a bit field [start:width] from word and returns the field */
|
|
var le = 48-start-width; // lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
return (le == 0 ? word : (word - word % (p = pow2[le]))/p) % pow2[width];
|
|
}
|
|
|
|
/**************************************/
|
|
function spout(text) {
|
|
/* Appends "text"+NL as a new text node to the panel DOM element */
|
|
var e = document.createTextNode(text + "\n");
|
|
|
|
panel.appendChild(e);
|
|
$$("PageBottom").scrollIntoView();
|
|
}
|
|
|
|
/**************************************/
|
|
function clearPanel() {
|
|
/* Clears the text panel */
|
|
var kid;
|
|
|
|
while (kid = panel.firstChild) {
|
|
panel.removeChild(kid);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function rtrim(s) {
|
|
/* Trims trailing spaces from "s" and returns the resulting string */
|
|
var m = s.match(/^(.*?) *$/);
|
|
|
|
return m[1];
|
|
}
|
|
|
|
/**************************************/
|
|
function padToLength(text, len) {
|
|
/* Converts the input string "text" to exactly "len" characters,
|
|
truncating or padding on the right with spaces as necessary */
|
|
var x = text.length;
|
|
|
|
if (x > len) {
|
|
return text.substring(0, len);
|
|
} else {
|
|
x = len-x;
|
|
while (x-- > 0) {
|
|
text += " ";
|
|
}
|
|
return text;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function stringToANSI(text, bytes, bx, asBinary) {
|
|
/* Translates the characters in a string to upper case, and then to ANSI
|
|
byte-array format. "text" is the input string, "bytes" is the Uint8Array
|
|
output buffer, and "bx" is the offset into that output buffer. If "asBinary" is
|
|
truthy, the translation is binary, otherwise it is done as BCLANSI */
|
|
var len = text.length;
|
|
var table1 = (asBinary ? BICtoANSI : BICtoBCLANSI);
|
|
var utxt = text.toUpperCase();
|
|
var x;
|
|
|
|
bx = bx || 0;
|
|
for (x=0; x<len; x++) {
|
|
bytes[bx++] = table1[ANSItoBIC[utxt.charCodeAt(x) & 0xFF]];
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function wordsToANSI(words, wx, wLength, bytes, bx, asBinary) {
|
|
/* Translates an array of B5500 words to ANSI byte-array format.
|
|
"words" = the array of words
|
|
"wx" = the starting index in "words"
|
|
"wLength" = the number of words to translate
|
|
"bytes" = a Uint8Array array
|
|
"bx" = the starting index in "bytes" to store the translated data
|
|
"asBinary" = if truthy, then binary translation is done; otherwise
|
|
B5500 BCLANSI translation is done */
|
|
var c;
|
|
var table = (asBinary ? BICtoANSI : BICtoBCLANSI);
|
|
var w;
|
|
var x;
|
|
var y;
|
|
var z;
|
|
|
|
bx = bx || 0;
|
|
if (wLength < 0) {
|
|
wLength = -wLength;
|
|
}
|
|
for (x=0; x<wLength; x++) {
|
|
w = words[wx+x] || 0;
|
|
for (y=0; y<8; y++) {
|
|
z = w % 0x40000000000;
|
|
c = (w-z)/0x40000000000;
|
|
bytes[bx++] = table[c];
|
|
w = z*64;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function wordsToString(words, wx, wLength, asBinary) {
|
|
/* Translates an array of B5500 words to a string and returns the string.
|
|
"words" = the array of words
|
|
"wx" = the starting index in "words"
|
|
"wLength" = the number of words to translate
|
|
"asBinary" = if truthy, then binary translation is done; otherwise
|
|
B5500 BCLANSI translation is done */
|
|
var c;
|
|
var table = (asBinary ? BICtoANSI : BICtoBCLANSI);
|
|
var text = "";
|
|
var w;
|
|
var x;
|
|
var y;
|
|
var z;
|
|
|
|
if (wLength < 0) {
|
|
wLength = -wLength;
|
|
}
|
|
for (x=0; x<wLength; x++) {
|
|
w = words[wx+x] || 0;
|
|
for (y=0; y<8; y++) {
|
|
z = w % 0x40000000000;
|
|
c = (w-z)/0x40000000000;
|
|
text += String.fromCharCode(table[c]);
|
|
w = z*64;
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
/**************************************/
|
|
function ANSItoWords(bytes, bx, bLength, words, wx, asBinary) {
|
|
/* Translates a portion of an ANSI byte array to a sequence of B5500 words.
|
|
"bytes" = the Uint8Array byte array
|
|
"bx" = 0-relative offset into "bytes"
|
|
"bLength" = number of bytes to translate
|
|
"words" = the word array
|
|
"wx" = 0-relative offset into "words" to store the translated data
|
|
"asBinary" = if truthy, then binary translation is done; otherwise
|
|
B5500 BCLANSI translation is done */
|
|
var cx = 0;
|
|
var w = 0;
|
|
var table = (asBinary ? ANSItoBIC : BCLANSItoBIC);
|
|
var x;
|
|
|
|
wx = wx || 0;
|
|
if (bLength < 0) {
|
|
bLength = -bLength;
|
|
}
|
|
for (x=0; x<bLength; x++) {
|
|
if (cx >= 8) {
|
|
words[wx++] = w;
|
|
w = cx = 0;
|
|
}
|
|
w = w*64 + table[bytes[bx+x]];
|
|
cx++;
|
|
}
|
|
while (cx++ < 8) {
|
|
w *= 64;
|
|
}
|
|
words[wx++] = w;
|
|
}
|
|
|
|
/**************************************/
|
|
function readDiskBlock(addr, segs, block, callback) {
|
|
/* Reads a block from the disk "eu" at "addr" for "segs" segments, translates
|
|
it to words in the "block" array, then calls "callback" passing the address
|
|
and block */
|
|
var bx = 0;
|
|
var eu;
|
|
var euAddr = addr % 1000000;
|
|
var euNr = (addr % 10000000 - euAddr)/1000000;
|
|
var euName = euPrefix + euNr.toString();
|
|
var endAddr = euAddr + segs - 1;
|
|
var nextAddr = euAddr;
|
|
var range = IDBKeyRange.bound(euAddr, endAddr);
|
|
var req;
|
|
var txn;
|
|
var x;
|
|
|
|
txn = disk.transaction(euName);
|
|
eu = txn.objectStore(euName);
|
|
req = eu.openCursor(range);
|
|
|
|
req.onsuccess = function(ev) {
|
|
var cursor = ev.target.result;
|
|
|
|
if (cursor) {
|
|
while (cursor.key > nextAddr) {
|
|
for (x=0; x<30; x++) {
|
|
block[bx++] = 0;
|
|
}
|
|
nextAddr++;
|
|
}
|
|
ANSItoWords(cursor.value, 0, 240, block, bx);
|
|
bx += 30;
|
|
nextAddr++;
|
|
cursor.continue();
|
|
} else {
|
|
while (nextAddr <= endAddr) {
|
|
for (x=0; x<30; x++) {
|
|
block[bx++] = 0;
|
|
}
|
|
nextAddr++;
|
|
}
|
|
callback(addr, block);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**************************************/
|
|
function readDiskHeader(block) {
|
|
/* Decodes "block" as a B5500 disk header, returning the header object */
|
|
var header = {
|
|
recordLength: 0,
|
|
blockLength: 0,
|
|
recordsPerBlock: 0,
|
|
segmentsPerBlock: 0,
|
|
logCreationDate: 0,
|
|
logCreationTime: 0,
|
|
lastAccessDate: 0,
|
|
creationDate: 0,
|
|
fileClass: 0,
|
|
fileType: 0,
|
|
recordCount: 0,
|
|
segmentsPerRow: 0,
|
|
maxRows: 0,
|
|
rowAddress: [],
|
|
words: []};
|
|
|
|
header.recordLength = fieldIsolate(block[0], 0, 15);
|
|
header.blockLength = fieldIsolate(block[0], 15, 15);
|
|
header.recordsPerBlock = fieldIsolate(block[0], 30, 12);
|
|
header.segmentsPerBlock = fieldIsolate(block[0], 42, 6);
|
|
header.logCreationDate = fieldIsolate(block[1], 6, 18);
|
|
header.logCreationTime = fieldIsolate(block[1], 25, 23);
|
|
header.lastAccessDate = fieldIsolate(block[3], 12, 18);
|
|
header.creationDate = fieldIsolate(block[3], 30, 18);
|
|
header.fileClass = fieldIsolate(block[4], 9, 2);
|
|
header.fileType = fieldIsolate(block[4], 36, 6);
|
|
header.recordCount = block[7];
|
|
header.segmentsPerRow = block[8];
|
|
header.maxRows = fieldIsolate(block[9], 43, 5);
|
|
header.rowAddress = block.slice(10);
|
|
header.words = block; // save the raw header words
|
|
|
|
if (header.recordLength == 0 || header.blockLength == 0) {
|
|
header.recordLength = header.blockLength = 30;
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
/**************************************/
|
|
function listFile(fileName) {
|
|
/* Opens a sub-window and lists the specified file as text to that window */
|
|
var block;
|
|
var doc;
|
|
var header = headers[fileName];
|
|
var htmlMatch = /[<>&"]/g;
|
|
var recNr = 0;
|
|
var rowAddr;
|
|
var rowNr = 0;
|
|
var rowEnd;
|
|
var win;
|
|
|
|
function htmlFilter(char) {
|
|
/* Used to escape HTML-sensitive characters in a string */
|
|
switch (char) {
|
|
case "&":
|
|
return "&";
|
|
case "<":
|
|
return "<";
|
|
case ">":
|
|
return ">";
|
|
case "\"":
|
|
return """;
|
|
default:
|
|
return char;
|
|
}
|
|
}
|
|
|
|
function listFileBlock(addr, block) {
|
|
/* Lists a block of a file, and if appropriate, calls itself for the next block */
|
|
var text;
|
|
var x;
|
|
|
|
for (x=0; x<header.blockLength; x+=header.recordLength) {
|
|
text = wordsToString(block, x, header.recordLength, true);
|
|
text = text.replace(htmlMatch, htmlFilter);
|
|
doc.writeln(text);
|
|
if (++recNr > header.recordCount) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
rowAddr += header.segmentsPerBlock;
|
|
if (recNr > header.recordCount) {
|
|
rowNr = header.maxRows;
|
|
listFileRow(listFileBlock);
|
|
} else {
|
|
if (rowAddr < rowEnd) {
|
|
readDiskBlock(rowAddr, header.segmentsPerBlock, block, listFileBlock);
|
|
} else {
|
|
rowNr++;
|
|
listFileRow(listFileBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
function listPBDBlock(addr, block) {
|
|
/* Lists a block of a printer-backup file, and if appropriate, calls itself for the next block */
|
|
var ctl;
|
|
var eof;
|
|
var memInhibit;
|
|
var logicalRecNr;
|
|
var skip;
|
|
var space;
|
|
var text;
|
|
var wordCount;
|
|
var x;
|
|
|
|
for (x=header.blockLength-1; x>0; x-=18) {
|
|
ctl = block[x];
|
|
logicalRecNr = ctl % 0x8000;
|
|
skip = (ctl % 0x80000 - ctl % 0x8000)/0x8000;
|
|
space = (ctl % 0x200000 - ctl % 0x80000)/0x80000;
|
|
eof = (ctl % 0x10000000 - ctl % 0x8000000)/0x8000000;
|
|
memInhibit = (ctl % 0x40000000 - ctl % 0x20000000)/0x20000000;
|
|
wordCount = (ctl % 0x8000000000 - ctl % 0x40000000)/0x40000000;
|
|
|
|
if (memInhibit) {
|
|
text = "";
|
|
} else {
|
|
text = rtrim(wordsToString(block, x-17, wordCount, true));
|
|
text = text.replace(htmlMatch, htmlFilter);
|
|
}
|
|
doc.writeln(text); // can't handle space==0 overprinting yet
|
|
if (skip > 0) {
|
|
doc.writeln("<hr>");
|
|
} else if (space & 0x01) {
|
|
doc.writeln(); // double space after print
|
|
}
|
|
if (eof) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
rowAddr += header.segmentsPerBlock;
|
|
recNr++;
|
|
if (eof || recNr > header.recordCount) {
|
|
rowNr = header.maxRows;
|
|
listFileRow(listPBDBlock);
|
|
} else {
|
|
if (rowAddr < rowEnd) {
|
|
readDiskBlock(rowAddr, header.segmentsPerBlock, block, listPBDBlock);
|
|
} else {
|
|
rowNr++;
|
|
listFileRow(listPBDBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
function listFileRow(lister) {
|
|
/* Drives the listing of one row of the file */
|
|
|
|
if (rowNr >= header.maxRows) {
|
|
doc.writeln("</pre>");
|
|
doc.close();
|
|
} else {
|
|
rowAddr = header.rowAddress[rowNr];
|
|
if (rowAddr) {
|
|
rowEnd = rowAddr + header.segmentsPerRow - 1;
|
|
readDiskBlock(rowAddr, header.segmentsPerBlock, block, lister);
|
|
} else {
|
|
rowNr++;
|
|
listFileRow(lister);
|
|
}
|
|
}
|
|
}
|
|
|
|
win = window.open("", "FileListWin", "resizable,scrollbars," +
|
|
",left=" + screen.availWidth*0.10 + ",top=" + screen.availHeight*0.10 +
|
|
",width=" + screen.availWidth*0.90 + ",height=" + screen.availHeight*0.90);
|
|
win.focus();
|
|
doc = win.document;
|
|
doc.open();
|
|
|
|
doc.writeln(fileName);
|
|
doc.writeln("<br><pre>");
|
|
doc.title = fileName;
|
|
|
|
block = new Array(header.blockLength);
|
|
if (fileName.indexOf("PBD/") == 0 & header.recordLength == 90 && header.blockLength == 90) {
|
|
listFileRow(listPBDBlock);
|
|
} else {
|
|
listFileRow(listFileBlock);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function formatDirectoryEntry(body, mfid, fid, header) {
|
|
/* Formats the disk header for a file */
|
|
var cell;
|
|
var e;
|
|
var fileName;
|
|
var row = document.createElement("tr");
|
|
var rx;
|
|
var text = "";
|
|
|
|
function appendCell(row, v, className) {
|
|
var cell = document.createElement("td");
|
|
|
|
if (className && className.search(/\S/) >= 0) {
|
|
cell.className = className;
|
|
}
|
|
cell.appendChild(document.createTextNode(v.toString()));
|
|
row.appendChild(cell);
|
|
}
|
|
|
|
fileName = rtrim(mfid.substring(1)) + "/" + rtrim(fid.substring(1));
|
|
cell = document.createElement("td");
|
|
cell.className = "mono";
|
|
e = document.createElement("a");
|
|
e.appendChild(document.createTextNode(fileName));
|
|
e.href = "#";
|
|
e.addEventListener("click", (function(name) {
|
|
return function(ev) {ev.preventDefault(); listFile(name)};
|
|
})(fileName));
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
|
|
appendCell(row, header.recordLength, "rj");
|
|
appendCell(row, header.blockLength, "rj");
|
|
appendCell(row, header.recordsPerBlock, "rj");
|
|
appendCell(row, header.segmentsPerBlock, "rj");
|
|
appendCell(row, header.logCreationDate);
|
|
appendCell(row, header.logCreationTime);
|
|
appendCell(row, header.lastAccessDate);
|
|
appendCell(row, header.creationDate);
|
|
appendCell(row, header.fileClass, "rj");
|
|
appendCell(row, header.fileType, "rj");
|
|
appendCell(row, header.recordCount, "rj");
|
|
appendCell(row, header.segmentsPerRow, "rj");
|
|
appendCell(row, header.maxRows, "rj");
|
|
body.appendChild(row);
|
|
|
|
headers[fileName] = header;
|
|
}
|
|
|
|
/**************************************/
|
|
function directoryList(successor) {
|
|
/* Reads the existing directory structure to generate a list of the files
|
|
in the directory. When finished, calls the "successor" function */
|
|
var block = new Array(480);
|
|
var body = $$("DirListBody");
|
|
|
|
function reportDirBlock(addr, block) {
|
|
/* Lists the entries in the resulting block; if not the last block,
|
|
advances to the next block */
|
|
var atEnd = false;
|
|
var bx;
|
|
var fid;
|
|
var mfid;
|
|
var namex;
|
|
|
|
// Step through the file name entries backwards
|
|
for (namex=14; namex>=0; namex--) {
|
|
bx = namex*2 + 450;
|
|
if (block[bx] == 0x4C) { // 0x4C=@114, end-of-directory marker
|
|
atEnd = true;
|
|
break;
|
|
} else if (block[bx] != 0x0C) { // 0x0C=@14, available directory slot
|
|
// Got a live one -- format its header
|
|
mfid = wordsToString(block, bx, 1, true);
|
|
fid = wordsToString(block, bx+1, 1, true);
|
|
bx = namex*30;
|
|
formatDirectoryEntry(body, mfid, fid, readDiskHeader(block.slice(bx, bx+30)));
|
|
}
|
|
}
|
|
if (atEnd) {
|
|
successor();
|
|
} else {
|
|
readDiskBlock(addr+16, 16, block, reportDirBlock);
|
|
}
|
|
|
|
}
|
|
|
|
/***** outer block of directoryList *****/
|
|
|
|
while (body.firstChild) {
|
|
body.removeChild(body.firstChild);
|
|
}
|
|
|
|
if (!config.EU0) {
|
|
alert("No EU0 in disk configuration -- cannot load");
|
|
} else {
|
|
readDiskBlock(directoryTop+4, 16, block, reportDirBlock);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function genericDBError(ev) {
|
|
/* Formats a generic alert when otherwise-unhandled database errors occur */
|
|
var disk = ev.currentTarget.result;
|
|
|
|
alert("Database \"" + disk.name + "\" error: " + ev.target.result.error);
|
|
}
|
|
|
|
/**************************************/
|
|
function openDatabase(name, successor) {
|
|
/* Attempts to open the disk subsystem database for the specified "name".
|
|
Stores the IDB database object in "disk" if successful, or stores null
|
|
if unsuccessful. Also gets directoryTop from seg 0 */
|
|
var block = new Array(30);
|
|
var db = null;
|
|
var req;
|
|
|
|
req = window.indexedDB.open(name); // open current version
|
|
|
|
req.onerror = function(ev) {
|
|
alert("Cannot open disk database: " + ev.target.error);
|
|
};
|
|
|
|
req.onblocked = function(ev) {
|
|
alert("Database.open is blocked -- cannot continue");
|
|
};
|
|
|
|
req.onsuccess = function(ev) {
|
|
disk = ev.target.result; // save the object reference globally for later use
|
|
disk.onerror = genericDBError;
|
|
// alert("Disk database opened: " + name + " #" + disk.version);
|
|
|
|
disk.transaction("CONFIG").objectStore("CONFIG").get(0).onsuccess = function(ev) {
|
|
config = ev.target.result;
|
|
readDiskBlock(0, 1, block, function(addr, block) {
|
|
directoryTop = block[1];
|
|
directoryEnd = block[4];
|
|
successor();
|
|
});
|
|
};
|
|
};
|
|
|
|
}
|
|
|
|
/**************************************/
|
|
function getDBName(defaultName) {
|
|
/* Parses the URL query string for a "db=name" parameter. If "db" is
|
|
found, returns the corresponding name; if not found, returns "defaultName" */
|
|
var args;
|
|
var i;
|
|
var name;
|
|
var search = location.search.substring(1); // drop the "?"
|
|
var value = defaultName;
|
|
var x;
|
|
|
|
args = search.split("&");
|
|
for (x=args.length-1; x>=0; --x) {
|
|
i = args[x].indexOf("=");
|
|
if (i > 0 ) {
|
|
name = decodeURIComponent(args[x].substring(0, i));
|
|
if (name.toLowerCase() == "db") {
|
|
value = decodeURIComponent(args[x].substring(i+1));
|
|
break; // out of for loop
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**************************************/
|
|
function checkBrowser() {
|
|
/* Checks whether this browser can support the necessary stuff */
|
|
var missing = "";
|
|
|
|
if (!window.File) {missing += ", File"}
|
|
if (!window.FileReader) {missing += ", FileReader"}
|
|
if (!window.FileList) {missing += ", FileList"}
|
|
if (!window.Blob) {missing += ", Blob"}
|
|
if (!window.ArrayBuffer) {missing += ", ArrayBuffer"}
|
|
if (!window.DataView) {missing += ", DataView"}
|
|
if (!window.indexedDB) {missing += ", IndexedDB"}
|
|
|
|
if (missing.length == 0) {
|
|
return false;
|
|
} else {
|
|
alert("No can do... your browser does not support the following features:\n" +
|
|
missing.substring(2));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/********** Start of window.onload() **********/
|
|
|
|
if (!checkBrowser()) {
|
|
dbName = getDBName(dbName);
|
|
openDatabase(dbName, function() {
|
|
directoryList(function() {});
|
|
});
|
|
}
|
|
}, false);
|
|
</script>
|
|
|
|
<style>
|
|
BODY {
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: small}
|
|
TABLE {
|
|
border-collapse: collapse}
|
|
TH {
|
|
vertical-align: bottom}
|
|
.center {
|
|
text-align: center}
|
|
.rj {
|
|
text-align: right}
|
|
.mono {
|
|
font-family: Courier New, Courier, monospace}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div style="position:relative; width:100%; height:3em">
|
|
<div style="position:absolute; left:0; top:0; width:auto">
|
|
<img src="../webUI/resources/retro-B5500-Logo.png" alt="retro-B5500 Logo" style="float:left">
|
|
Disk File List Utility
|
|
</div>
|
|
</div>
|
|
|
|
<table id=DirListTable border=1 cellspacing=0 cellpadding=1>
|
|
<thead>
|
|
<tr>
|
|
<th>File Name
|
|
<th>REC
|
|
<th>BLK
|
|
<th>RPB
|
|
<th>SPB
|
|
<th>LCD
|
|
<th>LCT
|
|
<th>LAD
|
|
<th>CRD
|
|
<th>FCL
|
|
<th>FTY
|
|
<th>CNT
|
|
<th>SPR
|
|
<th>MXR
|
|
<tbody id=DirListBody>
|
|
</table>
|
|
|
|
<pre id=TextPanel>
|
|
</pre>
|
|
|
|
<div id=PageBottom>
|
|
</div>
|
|
|
|
</body>
|
|
</html> |