/*********************************************************************** * retro-b5500/webUI B5500SystemConfig.js ************************************************************************ * Copyright (c) 2014, Nigel Williams and Paul Kimpel. * Licensed under the MIT License, see * http://www.opensource.org/licenses/mit-license.php ************************************************************************ * B5500 System Configuration management object. * * Defines the system configuration used internally by the emulator and the * methods used to manage that configuration data. * * The configuration data consists of two parts, (a) the processors, I/O * units, memory modules, and peripheral devices that make up the system, * and (b) a disk storage configuration that defines the disk Electronics * Units (EU) and sizes and types of each of the EUs. This module addresses * the first part. * * Each system configuration is identified by a unique "configName" property. * Each system configuration specifies the components in item (a) above, * plus the name of a disk storage configuration that is currently associated * with that system configuration. The disk storage configurations themselves * are maintained in the storage databases described below. Thus, multiple * system configurations can be defined to use the same storage configuration, * and the storage used with a given system configuration can, within some * limits, be changed. Only one system configuration at a time can be the * default (or active) one, however. * * * The system configuration data is persisted in an IndexedDB database, * "retro-B5500-Config", that contains the following object stores: * * 1. Global: * This is a single-object store that holds global values for the * emulator, e.g., the name of the current default system configuration. * * 2. SysConfig: * This holds one system configuration object for each system that * has been defined to the emulator. The store is indexed by the * "configName" property of the configuration object. * * 3. StorageNames: * Each object in this store is simply a string giving the name of one * of the disk storage subsystem databases that have been previously * defined. This structure is necessary, as there is no facility in * IndexedDB to enumerate the databases that currently exist for an * origin. The store is indexed by the value of the string. * * See the constructor below for the structure of the system configuration * data. * * Each disk storage subsystem is maintained as a separate IndexedDB database. * See the B5500DiskStorageConfig.js module for the configuration * management interface for disk subsystems. * ************************************************************************ * 2014-08-10 P.Kimpel * Original version, from thin air. ***********************************************************************/ "use strict"; /**************************************/ function B5500SystemConfig() { /* Constructor for the SystemConfig configuration management object */ this.db = null; // the IndexedDB database connection (null if closed) this.systemConfig = null; // the currently-loaded system configuration this.window = null; // configuration UI window object this.alertWin = window; // current base window for alert/confirm/prompt } /**************************************/ B5500SystemConfig.prototype.configDBName = "retro-B5500-Config"; B5500SystemConfig.prototype.configDBVersion = 1; B5500SystemConfig.prototype.sysGlobalName = "Global"; B5500SystemConfig.prototype.sysConfigName = "SysConfig"; B5500SystemConfig.prototype.sysStorageNamesName = "StorageNames"; // Template for the Global configuration store B5500SystemConfig.prototype.globalConfig = { currentConfigName: B5500SystemConfiguration.prototype.sysDefaultConfigName }; // Template for current system configuration properties B5500SystemConfig.prototype.systemConfig = B5500SystemConfiguration.prototype.systemConfig; /**************************************/ B5500SystemConfig.prototype.$$ = function $$(id) { return this.window.document.getElementById(id); } /**************************************/ B5500SystemConfig.prototype.genericDBError = function genericDBError(ev) { // Formats a generic alert message when an otherwise-unhandled database error occurs */ window.alert("Database \"" + target.result.name + "\" UNHANDLED ERROR: " + ev.target.result.error); }; /**************************************/ B5500SystemConfig.prototype.upgradeConfigSchema = function upgradeConfigSchema(ev) { /* Handles the onupgradeneeded event for the System Configuration database. Upgrade the schema to the current version. For a new database, creates the default configuration and stores it in the database. "ev" is the upgradeneeded event. Must be called in the context of the SystemConfiguration object */ var configStore = null; var db = ev.target.result; var globalStore = null; var namesStore = null; var req = ev.target; var txn = req.transaction; switch (true) { case ev.oldVersion < 1: if (!this.alertWin.confirm("The retro-B5500 System Configuration database\n" + "does not exist. Do you want to create it?")) { txn.abort(); db.close(); this.alertWin.alert("System Configuration database creation refused --\n" + "CANNOT CONTINUE."); } else { globalStore = db.createObjectStore(this.sysGlobalName); configStore = db.createObjectStore( this.sysConfigName, {keyPath: "configName", unique: true}); // The StorageNames store holds strings, so the keypath is simply the stored value. namesStore = db.createObjectStore( this.sysStorageNamesName, {keyPath: "", unique: true}); // Populate a default initial configuration. globalStore.put(B5500SystemConfig.prototype.globalConfig, 0); configStore.put(B5500SystemConfig.prototype.systemConfig); namesStore.put(B5500SystemConfig.prototype.systemConfig.units.DKA.storageName || B5500SystemConfig.prototype.systemConfig.units.DKB.storageName); this.alertWin.alert("System Configuration database created...\n" + "An initial configuration \"" + B5500SystemConfig.prototype.systemConfig.configName + "\" was created and set as current."); } break; default: this.alertWin.alert("System Configuration database at\n" + "higher version than implementation --\nCANNOT CONTINUE"); } // switch }; /**************************************/ B5500SystemConfig.prototype.deleteConfigDB = function deleteConfigDB(onsuccess, onfailure) { /* Attempts to permanently delete the System Configuration database. If successful, calls the "onsuccess" function passing the resulting event; if not successful, calls onfailure passing the resulting event */ var req; var that = this; if (this.alertWin.confirm("This will PERMANENTLY DELETE the emulator's\n" + "System Configuration Database.\n\n" + "Are you sure you want to do this?\n")) { if (this.alertWin.confirm("Deletion of the Configuration Database\n" + "CANNOT BE UNDONE.\n\nAre you really sure?\n")) { req = window.indexedDB.deleteDatabase(this.configDBName); req.onerror = function(ev) { that.alertWin.alert("CANNOT DELETE the System Configuration database:\n" + ev.target.error); onfailure(ev); }; req.onblocked = function(ev) { that.alertWin.alert("Deletion of the System Configuration database is BLOCKED"); }; req.onsuccess = function(ev) { that.alertWin.alert("System Configuration database successfully deleted."); onsuccess(ev); }; } } }; /**************************************/ B5500SystemConfig.prototype.openConfigDB = function openConfigDB(onsuccess, onfailure) { /* Attempts to open the System Configuration database. Handles, if necessary, a change in database version. If successful, calls the "onsuccess" function passing the success event. If not successful, calls the "onfailure" function passing the error event */ var req; // IndexedDB open request var that = this; req = window.indexedDB.open(this.configDBName, this.configDBVersion); req.onblocked = function(ev) { that.alertWin.alert("Database \"" + that.configDBName + "\" open is blocked"); that.closeConfigDB(); }; req.onupgradeneeded = function(ev) { that.upgradeConfigSchema(ev); }; req.onerror = function(ev) { onfailure(ev); that.db = null; }; req.onsuccess = function(ev) { that.db = ev.target.result; that.db.onerror = that.genericDBError; // set up global error handler delete that.globalConfig; delete that.systemConfig; onsuccess(ev); }; }; /**************************************/ B5500SystemConfig.prototype.closeConfigDB = function closeConfigDB() { /* Closes the IndexedDB instance if it is open */ if (this.db) { this.db.close(); this.db = null; } }; /**************************************/ B5500SystemConfig.prototype.getSystemConfig = function getSystemConfig(configName, successor) { /* Attempts to retrieve the system configuration structure under "configName". If "configName" is falsy, retrieves the current default configuration. If successful, calls the "successor" function passing the configuration object; otherwise calls "successor" passing null. Closes the database after a successful get. Displays alerts for any errors encountered */ var that = this; function readConfig(configName) { /* Reads the named system configuration structure from the database, then calls the successor function with the configuration object */ var txn = that.db.transaction(that.sysConfigName); txn.onerror = function(ev) { successor(null); that.alertWin.alert("getSystemConfig cannot get config data:\n" + ev.target.error); } txn.objectStore(that.sysConfigName).get(configName || "").onsuccess = function(ev) { that.systemConfig = ev.target.result; successor(that.systemConfig); }; } function getConfig() { /* Retrieve the configuration specified by the outer "configName" parameter. Unconditionally retrieve the global configuration structure. If configName is falsy, retrieve the configuration with the current configuration name in the global structure, otherwise retrieve the one named by "configName" */ var txn; if (configName) { readConfig(configName); } else { txn = that.db.transaction(that.sysGlobalName); txn.onerror = function(ev) { that.alertWin.alert("getSystemConfig cannot get default config name:\n" + ev.target.error); successor(null); } txn.objectStore(that.sysGlobalName).get(0).onsuccess = function(ev) { that.globalConfig = ev.target.result; readConfig(that.globalConfig.currentConfigName); } }; } function onOpenSuccess(ev) { getConfig(); } function onOpenFailure(ev) { that.systemConfig = null; successor(null); that.alertWin.alert("getSystemConfig cannot open \"" + that.configDBName + "\" database:\n" + ev.target.error); } if (this.db) { getConfig(); } else { this.openConfigDB(onOpenSuccess, onOpenFailure); } }; /**************************************/ B5500SystemConfig.prototype.putSystemConfig = function putSystemConfig( config, successor) { /* Attempts to store the system configuration structure "config" to the database. The configuration name must be in config.configName. If a configuration by that name already exists, it will be replaced by "config". Unconditionally sets this as the current system configuration. If successful, calls "successor" passing the success event */ var that = this; function putConfig() { var txn = that.db.transaction([that.sysGlobalName, that.sysConfigName], "readwrite"); txn.oncomplete = function(ev) { that.systemConfig = config; successor(ev); }; that.globalConfig.currentConfigName = config.configName; txn.objectStore(that.sysGlobalName).put(that.globalConfig, 0); txn.objectStore(that.sysConfigName).put(config); } function onOpenSuccess(ev) { putConfig(); } function onOpenFailure(ev) { that.systemConfig = null; that.alertWin.alert("putSystemConfig cannot open \"" + that.configDBName + "\" database:\n" + ev.target.error); } if (this.db) { putConfig(); } else { this.openConfigDB(onOpenSuccess, onOpenFailure); } }; /**************************************/ B5500SystemConfig.prototype.deleteSystemConfig = function deleteSystemConfig( configName, successor) { /* Attempts to delete the system configuration structure identified by "configName" from the database. If successful, calls "successor" passing the success event */ var that = this; function deleteConfig() { var txn = that.db.transaction([that.sysGlobalName, that.sysConfigName], "readwrite"); txn.oncomplete = function(ev) { if (that.systemConfig.configName == configName) { delete that.systemConfig; } successor(ev); }; txn.objectStore(that.sysConfigName).delete(configName); if (that.globalConfig.currentConfigName == configName) { that.globalConfig.currentConfigName = ""; txn.objectStore(that.sysGlobalName).put(that.globalConfig, 0); } } function onOpenSuccess(ev) { deleteConfig(); } function onOpenFailure(ev) { that.systemConfig = null; that.alertWin.alert("deleteSystemConfig cannot open \"" + that.configDBName + "\" database:\n" + ev.target.error); } if (this.db) { deleteConfig(); } else { this.openConfigDB(onOpenSuccess, onOpenFailure); } }; /**************************************/ B5500SystemConfig.prototype.addStorageName = function addStorageName( storageName, successor) { /* Adds a storage subsystem name to the "StorageNames" object store. If successful, calls "successor" passing the success event */ var that = this; function addName() { var txn = that.db.transaction(that.sysStorageNamesName, "readwrite"); txn.oncomplete = function(ev) { successor(ev); }; txn.objectStore(that.sysStorageNamesName).put(storageName); } function onOpenSuccess(ev) { addName(); } function onOpenFailure(ev) { that.systemConfig = null; that.alertWin.alert("addStorageName cannot open \"" + that.configDBName + "\" database:\n" + ev.target.error); } if (this.db) { addName(); } else { this.openConfigDB(onOpenSuccess, onOpenFailure); } }; /**************************************/ B5500SystemConfig.prototype.removeStorageName = function removeStorageName( storageName, successor) { /* Removes a storage subsystem name from the "StorageNames" object store. If successful, calls "successor" passing the success event */ var that = this; function removeName() { var txn = that.db.transaction(that.sysStorageNamesName, "readwrite"); txn.oncomplete = function(ev) { successor(ev); }; txn.objectStore(that.sysStorageNamesName).delete(storageName); } function onOpenSuccess(ev) { removeName(); } function onOpenFailure(ev) { that.systemConfig = null; that.alertWin.alert("removeStorageName cannot open \"" + that.configDBName + "\" database:\n" + ev.target.error); } if (this.db) { removeName(); } else { this.openConfigDB(onOpenSuccess, onOpenFailure); } }; /**************************************/ B5500SystemConfig.prototype.enumerateStorageNames = function enumerateStorageNames( successor) { /* Enumerates the storage subsystem keys in the "StorageNames" object store to an array. If successful, calls "successor" passing the success event and the array */ var that = this; function enumerateNames() { var names = []; var txn = that.db.transaction(that.sysStorageNamesName); txn.oncomplete = function(ev) { successor(ev, names); }; txn.objectStore(that.sysStorageNamesName).openCursor().onsuccess = function(ev) { var cursor = ev.target.result; if (cursor) { names.push(cursor.key); cursor.continue(); } }; } function onOpenSuccess(ev) { enumerateNames(); } function onOpenFailure(ev) { that.systemConfig = null; that.alertWin.alert("enumerateStoragesNames cannot open \"" + that.configDBName + "\" database:\n" + ev.target.error); } if (this.db) { enumerateNames(); } else { this.openConfigDB(onOpenSuccess, onOpenFailure); } }; /*********************************************************************** * System Configuration UI Support * ***********************************************************************/ /**************************************/ B5500SystemConfig.prototype.loadConfigDialog = function loadConfigDialog(config) { /* Loads the configuration UI window with the settings from "config" */ function loadNameList(listID, storeName, keyName) { /* Loads a list of names from the specified object store to a list to be loaded. "storeName" is the name of the object store. "keyName is the name to be selected in the list. If "keyName" does not exist in the list obtained from the store, it is unconditionally appended to the list and selected */ var list = this.$$(listID); var txn = this.db.transaction(storeName); var selected = false; while (list.length) { // empty the