User Tools

Site Tools


Install Manager Config (from DAZ Studio)

Summary

Below is an example demonstrating how one might go about parsing information from an .ini file, converting that information into a JSON object, providing a dialog that allows an end user to select options that modify the data, and then convert the data back into its original form - writing it back to the .ini file.

API Areas of Interest

Example

InstallManagerConfig_from_DAZStudio.dsa
// DAZ Studio version 4.7.0.12 filetype DAZ Script
 
// Define an anonymous function;
// serves as our main loop,
// limits the scope of variables
(function(){
 
	var g_sToolName = "Install Manager Configuration";
	var g_sScriptPath = getScriptFileName();
	var g_oScriptFile = new DzFileInfo( g_sScriptPath );
 
	// Install Manager configuration file keywords
	var g_sAccount = "Account";
	var g_sAccountTitle = "AccountTitle";
	var g_sAllowDesktopShorcuts = "AllowDesktopShorcuts";
	var g_sAllowStartMenuShorcuts = "AllowStartMenuShorcuts";
	var g_sAppBitArch = "AppBitArch";
	var g_sApplicationPaths = "ApplicationPaths";
	var g_sAppName = "AppName";
	var g_sAppPath = "AppPath";
	var g_sAppVersion = "AppVersion";
	var g_sAutoDelete = "AutoDelete";
	var g_sAutoInstall = "AutoInstall";
	var g_sCurInstallPath = "CurInstallPath";
	var g_sCurrentTabIndex = "CurrentTabIndex";
	var g_sDownloadDetailsToggled = "DownloadDetailsToggled";
	var g_sDownloadPath = "DownloadPath";
	var g_sDownloadSortIndex = "DownloadSortIndex";
	var g_sGeneral = "General";
	var g_sInstallDetailsToggled = "InstallDetailsToggled";
	var g_sInstalledDetailsToggled = "InstalledDetailsToggled";
	var g_sInstalledSortIndex = "InstalledSortIndex";
	var g_sInstallPath = "InstallPath";
	var g_sInstallPaths = "InstallPaths";
	var g_sInstallPathTitle = "InstallPathTitle";
	var g_sInstallSortIndex = "InstallSortIndex";
	var g_sMarkInstalledContentAsNew = "MarkInstalledContentAsNew";
	var g_sOverrideManifestDir = "OverrideManifestDir";
	var g_sOverrideThumbnailDir = "OverrideThumbnailDir";
	var g_sRememberPassword = "RememberPassword";
	var g_sShowProductInfo = "ShowProductInfo";
	var g_sShowTips = "ShowTips";
	var g_sSize = "size";
	var g_sSoftware32Path = "Software32Path";
	var g_sSoftware64Path = "Software64Path";
	var g_sTagID = "TagID";
	var g_sTags = "Tags";
	var g_sTagValue = "TagValue";
	var g_sUpdatesToPrevious = "UpdatesToPrevious";
 
	// Local keywords
	var g_sItems = "items";
 
	// Keyword collections
	var g_aMultiPartSectionNames = [ g_sInstallPaths, g_sApplicationPaths, g_sTags ];
	var g_aValidSectionNames = [ g_sGeneral ].concat( g_aMultiPartSectionNames );
 
	var g_aGeneralKeyNames = [ 
			g_sAccount, g_sAccountTitle, g_sAllowDesktopShorcuts, g_sAllowStartMenuShorcuts,
			g_sAutoDelete, g_sAutoInstall, g_sCurInstallPath, g_sCurrentTabIndex,
			g_sDownloadDetailsToggled, g_sDownloadPath, g_sDownloadSortIndex,
			g_sInstallDetailsToggled, g_sInstalledDetailsToggled, g_sInstalledSortIndex,
			g_sInstallSortIndex, g_sMarkInstalledContentAsNew, g_sOverrideManifestDir,
			g_sOverrideThumbnailDir, g_sRememberPassword, g_sShowProductInfo, g_sShowTips,
			g_sSoftware32Path, g_sSoftware64Path, g_sUpdatesToPrevious
		];
	var g_aInstallPathsKeyNames = [ g_sInstallPathTitle, g_sInstallPath ];
	var g_aApplicationPathsKeyNames = [ g_sAppName, g_sAppVersion, g_sAppBitArch, g_sAppPath ];
	var g_aTagsKeyNames = [ g_sTagID, g_sTagValue ];
 
	var g_aMultiPartKeyNames = [ g_sSize ].concat(
			g_aInstallPathsKeyNames, g_aApplicationPathsKeyNames, g_aTagsKeyNames );
 
	var g_aValidKeyNames = g_aGeneralKeyNames.concat( g_aMultiPartKeyNames );
 
	// Common strings
	var g_sNativeFormats = text( "DAZ Studio Formats" );
	var g_sPoserFormats = text( "Poser Formats" );
	var g_sOtherFormats = text( "Other Import Formats" );
 
	// Helpers
	var g_oStringHelper = new DzStringHelper();
	var g_oArrayHelper = new DzArrayHelper();
 
	// Listview created in one function; referenced in another
	var g_wRemoteView = undefined;
	var g_wLocalView = undefined;
 
	/*********************************************************************/
	// String : A function for retrieving a translation if one exists
	function text( sText )
	{
		// If the version of the application supports qsTr()
		if( typeof( qsTr ) != "undefined" ){
			// Return the translated (if any) text
			return qsTr( sText );
		}
 
		// Return the original text
		return sText;
	};
 
	/*********************************************************************/
	// Array : Checks unique items, unchecks non-unique items
	function setUniqueItems( aKnownValues, wListView, nColumn, bIgnoreDepth, nDepth )
	{
		// Initialize variables
		var aValues = aKnownValues;
		var nValues = aValues.length;
 
		// Declare working variables
		var wListViewItem;
		var sValue;
 
		// Get all of the items from the listview
		var aListItems = wListView.getItems( DzListView.All );
		// Iterate over the items
		for( var i = 0; i < aListItems.length; i += 1 ){
			// Get the 'current' item
			wListViewItem = aListItems[ i ];
			// If we are ignoring items and the item is at the ignore depth
			if( bIgnoreDepth && wListViewItem.depth() == nDepth ){
				// Uncheck it; if it's a controller it'll get checked
				// according to the checked state of it's children
				wListViewItem.on = false;
 
				// Next!!
				continue;
			}
 
			// Get the text from the item
			sValue = wListViewItem.text( nColumn );
 
			// Append the text to our list, if it is not already there
			aValues = g_oArrayHelper.addToArray( aValues, sValue );
			// If the text did not already exist in the list; the length of the array has grown
			if( aValues.length > nValues ){
				// Check the item; i.e. it's unique
				wListViewItem.on = true;
				// Update our count
				nValues = aValues.length;
			// If the path already existed
			} else {
				// Uncheck the item; i.e. it is not unique
				wListViewItem.on = false;
			}
		}
 
		// Return the [modified] array
		return aValues;
	};
 
	/*********************************************************************/
	// void : Checks uniquely mapped directory items, unchecks non-unique items
	function setUniqueMappedDirs()
	{
		// If our listviews are not defined
		if( typeof( g_wRemoteView ) == undefined ||
			typeof( g_wLocalView ) == undefined ){
			// We are done...
			return;
		}
 
		// Define common values
		var nDataColumn = 1;
		var bSkipRoot = true;
		var nRootDepth = 0;
 
		// Check the items that are unique
		var aRemotePaths = setUniqueItems( [], g_wRemoteView, nDataColumn, bSkipRoot, nRootDepth );
		var aUniquePaths = setUniqueItems( aRemotePaths, g_wLocalView, nDataColumn, bSkipRoot, nRootDepth );
	};
 
	/*********************************************************************/
	// void : Adds the mapped directories known by Install Manager
	function addRemoteMappedPaths( wListView, aPathItems )
	{
		// If we do not have paths
		if( aPathItems.length < 1 ){
			// We are done
			return;
		}
 
		// Declare working variables
		var wPathItem;
		var sPath;
		var oPathItem;
 
		// Create the root item
		var wRootItem = new DzCheckListItem( wListView, DzCheckListItem.CheckBoxController );
		// Set the text
		wRootItem.setText( 0, text( "All Current" ) );
		// Expand the item; show it's children
		wRootItem.open = true;
 
		// Iterate over the mapped items, in reverse order,
		// because we want the same order as they currently
		// exist, and adding an item prepends to the list
		for( var i = aPathItems.length; i > 0; i -= 1 ){
			// Get the 'current' item; we adjust the index
			// because we are iterating in reverse order
			oPathItem = aPathItems[ i - 1 ];
			// Get the path from the item
			sPath = oPathItem[ g_sInstallPath ];
 
			// Create an item for the path
			wPathItem = new DzCheckListItem( wRootItem, DzCheckListItem.CheckBox );
			// Set the label
			wPathItem.setText( 0, oPathItem[ g_sInstallPathTitle ] );
			// Set the path
			wPathItem.setText( 1, sPath );
		}
	};
 
	/*********************************************************************/
	// void : Adds the mapped directories known by DAZ Studio as items, based on type
	function addLocalMappedPaths( wListView, sType )
	{
		// Get the content manager
		var oContentMgr = App.getContentMgr();
 
		// Initialize the number of directories
		var nDirs = -1;
 
		// Based on type, set the number of directories
		switch( sType ){
			case g_sNativeFormats:
				nDirs = oContentMgr.getNumContentDirectories();
				break;
			case g_sPoserFormats:
				nDirs = oContentMgr.getNumPoserDirectories();
				break;
			case g_sOtherFormats:
				nDirs = oContentMgr.getNumImportDirectories();
				break;
			default:
				return;
		}
 
		// If we have directories
		if( nDirs > 0 ){
			// Create the root item for the type
			var wRootItem = new DzCheckListItem( wListView, DzCheckListItem.CheckBoxController );
			// Set the text to the type
			wRootItem.setText( 0, sType );
			// Expand the item; show it's children
			wRootItem.open = true;
 
			// Declare working variables
			var nDir;
			var sPath;
			var oFolder;
			var wPathItem;
 
			// Iterate over the directories, in reverse order,
			// because we want them displayed in the same order
			// as they currently exist, and adding an item
			// prepends to the list
			for( var i = nDirs; i > 0; i -= 1 ){
				// Adjust the index because we are iterating in reverse order
				nDir = (i - 1);
				// Based on type, get the n'th folder
				switch( sType ){
					case g_sNativeFormats:
						oFolder = oContentMgr.getContentDirectory( nDir );
						break;
					case g_sPoserFormats:
						oFolder = oContentMgr.getPoserDirectory( nDir );
						break;
					case g_sOtherFormats:
						oFolder = oContentMgr.getImportDirectory( nDir );
						break;
					default:
						oFolder = undefined;
						break;
				}
 
				// If we do not have a folder
				if( !oFolder ){
					// Next!!
					continue;
				}
 
				// Get the full path of the folder
				sPath = oFolder.fullPath;
 
				// Create an item for the path
				wPathItem = new DzCheckListItem( wRootItem, DzCheckListItem.CheckBox );
				// Set the label
				wPathItem.setText( 0, oFolder.label );
				// Set the path
				wPathItem.setText( 1, sPath );
			}
		}
	};
 
	/*********************************************************************/
	// Array : Retrieve checked path items from a listview
	function getCheckedPathItems( wListView )
	{
		// Define the array of items to be returned
		var aItems = [];
 
		// Declare working variables
		var wItem;
		var oItem;
 
		// Get the checked items
		var aCheckedItems = wListView.getItems( DzListView.Checked );
		// Iterate over the checked items
		for( var i = 0; i < aCheckedItems.length; i += 1 ){
			// Get the 'current' item
			wItem = aCheckedItems[ i ];
			// If the item is at the root depth; i.e. it's a checkbox controller
			if( wItem.depth() == 0 ){
				// Next!!
				continue;
			}
 
			// Define an object for the item
			oItem = {};
			// Set the label
			oItem[ g_sInstallPathTitle ] = wItem.text( 0 );
			// Set the path
			oItem[ g_sInstallPath ] = wItem.text( 1 );
 
			// Add the item to the return array
			aItems = g_oArrayHelper.addToArray( aItems, oItem );
		}
 
		// Return the path items
		return aItems;
	};
 
	/*********************************************************************/
	// Array : Prompt the user to choose directories to be mapped
	function getUserMappedDirs( sAccount, aAccountMappedItems )
	{
		// Define common values
		var sObjectNamePrefix = g_oStringHelper.stripSpaces( g_sToolName );
		var sLabel = text( "Label" );
		var sPath = text( "Path" );	
		var sInstallManager = text( "Install Manager" );
		var sContentPathShortcuts = text( "Content Path Shortcuts" );
		var sRemoteTitle = String("%1 %2 :").arg( sInstallManager ).arg( sContentPathShortcuts );
		var sLocalTitle = text( "%1 Mapped Directories :" ).arg( App.appName );
		var sSelectUniques = text( "Select Uniques" );
 
		// Create a basic dialog
		var wDlg = new DzBasicDialog();
		// Set the title
		wDlg.caption = String("%1 (%2)").arg( g_sToolName ).arg( sAccount );
 
		// Get the wrapped QWidget
		var oDlg = wDlg.getWidget();
		// Set the object name; used to store/retrieve unique dialog geometry
		oDlg.objectName = sObjectNamePrefix;
 
		// Create a label
		var wLbl = new DzLabel( wDlg );
		// Get the wrapped QWidget
		var oLbl = wLbl.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oLbl.objectName = String("%1DescriptionLbl").arg( sObjectNamePrefix );
		// Set styling options on the label
		wLbl.wordWrap = true;
		// If the application version is 4.10.0.22 or newer
		if( App.version64 >= 0x0004000a00000016 ){
			// Disable eliding
			wLbl.elideMode = DzWidget.ElideNone;
		}
		// Set the text
		wLbl.text = text( "Use the lists displayed below to select which paths to " +
			"map as %1 within %2.<br/><br/>The \"What's This?\" button, in the lower left " +
			"corner of this dialog, can be used to view more information about a " +
			"particular widget; simply press the button, then click on the widget." +
			"<hr>").arg( sContentPathShortcuts ).arg( sInstallManager );
		// Add the label to the dialog
		wDlg.addWidget( wLbl );
 
		// Create a group box
		var wRemoteGrpBx = new DzVGroupBox( wDlg );
		// Get the wrapped QWidget
		var oRemoteGrpBx = wRemoteGrpBx.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oRemoteGrpBx.objectName = String("%1RemoteGrpBx").arg( sObjectNamePrefix );
		// Set the title
		wRemoteGrpBx.title = sRemoteTitle;
		// Set styling options on the group
		wRemoteGrpBx.flat = true;
		wRemoteGrpBx.insideMargin = nMargin;	
		// Set the what's this text
		wRemoteGrpBx.whatsThis = String("<b>%1 %2</b><br/><br/>%3")
			.arg( sInstallManager )
			.arg( sContentPathShortcuts )
			.arg( text( "Use this list to indicate which of the %1 currently defined in " +
			"%2, for the current account, you would like to keep defined.<br/><br/>By " +
			"default, and upon pressing the \"%3\" button, all of the items in this " +
			"list that have a unique path will be checked; all items that do not have " +
			"a unique path will be unchecked.")
			.arg( sContentPathShortcuts )
			.arg( sInstallManager )
			.arg( sSelectUniques ) );
		// Add the group box to the dialog
		wDlg.addWidget( wRemoteGrpBx );
 
		// Get the current style
		var oStyle = App.getStyle();
		// Get pixel metrics from the style
		var nMargin = oStyle.pixelMetric( "DZ_GeneralMargin" );
		var nStepSize = oStyle.pixelMetric( "DZ_TreeStepSize" );
 
		// Create a listview; for remote mappings
		// we'll see why its defined as a global shortly
		g_wRemoteView = new DzListView( wRemoteGrpBx );
		// Get the wrapped QWidget
		var oListView = g_wRemoteView.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oListView.objectName = String("%1RemoteLView").arg( sObjectNamePrefix );
 
		// Add the label column
		g_wRemoteView.addColumn( sLabel );
		// Add the path column
		g_wRemoteView.addColumn( sPath );
		// Set selection to highlight all columns
		g_wRemoteView.allColumnsShowFocus = true;
		// Disable sorting; set the sorting column to none
		g_wRemoteView.setSorting( -1 );
		// Set styling options on the listview
		g_wRemoteView.itemMargin = nMargin;
		g_wRemoteView.treeStepSize = nStepSize;
		// Set the what's this text
		g_wRemoteView.whatsThis = wRemoteGrpBx.whatsThis;
 
		// Add items for the known mappings;
		addRemoteMappedPaths( g_wRemoteView, aAccountMappedItems );
 
		// Create a label
		var wLocalGrpBx = new DzVGroupBox( wDlg );
		// Get the wrapped QWidget
		var oLocalGrpBx = wLocalGrpBx.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oLocalGrpBx.objectName = String("%1LocalGrpBx").arg( sObjectNamePrefix );
		// Set the title
		wLocalGrpBx.title = sLocalTitle;
		// Set styling options on the group
		wLocalGrpBx.flat = true;
		wLocalGrpBx.insideMargin = nMargin;
		// Set the what's this text
		wLocalGrpBx.whatsThis = String("<b>%1</b><br/><br/>%2")
			.arg( sLocalTitle.replace( " :", "" ) )
			.arg( text( "Use this list to indicate which of the %1 you would like to " +
			"make available as shortcuts within %2, for the current account.<br/><br/>" +
			"Avoid selecting more than one entry with the same path in this list and " +
			"the \"%3\" list.  Use the \"%4\" button to help select the paths that are " +
			"unique in both lists.")
			.arg( sLocalTitle.replace( " :", "" ) )
			.arg( sInstallManager )
			.arg( sRemoteTitle.replace( " :", "" ) )
			.arg( sSelectUniques ) );
		// Add the label to the dialog
		wDlg.addWidget( wLocalGrpBx );
 
		// Create a listview; for locally known mappings
		// we'll see why its defined as a global shortly
		g_wLocalView = new DzListView( wLocalGrpBx );
		// Get the wrapped QWidget
		oListView = g_wLocalView.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oListView.objectName = String("%1LocalLView").arg( sObjectNamePrefix );
 
		// Add the label column
		g_wLocalView.addColumn( sLabel );
		// Add the path column
		g_wLocalView.addColumn( sPath );
		// Set selection to highlight all columns
		g_wLocalView.allColumnsShowFocus = true;
		// Disable sorting; set the sorting column to none
		g_wLocalView.setSorting( -1 );
		// Set styling options on the listview
		g_wLocalView.itemMargin = nMargin;
		g_wLocalView.treeStepSize = nStepSize;
		// Set the what's this text
		g_wLocalView.whatsThis = wLocalGrpBx.whatsThis;
 
		// Add items for the mapped directories;
		// we do this in reverse of the order we want them
		// to be displayed in, because adding an item
		// prepends it to the list and we are not sorting
		addLocalMappedPaths( g_wLocalView, g_sOtherFormats );
		addLocalMappedPaths( g_wLocalView, g_sPoserFormats );
		addLocalMappedPaths( g_wLocalView, g_sNativeFormats );
 
		// Set the [unique] paths checked by default
		setUniqueMappedDirs();
 
		// Create a button
		var wUniquesBtn = new DzPushButton( wDlg );
		// Get the wrapped QWidget
		var oUniquesBtn = wUniquesBtn.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oUniquesBtn.objectName = String("%1UniquesBtn").arg( sObjectNamePrefix );
		// Set the text
		wUniquesBtn.text = sSelectUniques;
		// Set the tooltip text
		wUniquesBtn.toolTip = text( "Click to restore the selection of unique paths in both lists" );
		// Set the what's this text
		wUniquesBtn.whatsThis = String("<b>%1</b><br/><br/>%2.<br/><br/>%3")
			.arg( wUniquesBtn.text ).arg( wUniquesBtn.toolTip )
			.arg( text( "The path of each item in the \"%1\" and \"%2\" lists are checked " +
				"for uniqueness.  If the path of an item is unique, that item becomes " +
				"checked.  If the path of an item is not unique, that item becomes " +
				"unchecked.  Root items are \"tri-state\"; they become checked or " +
				"unchecked according to the checked state of their respective children.")
				.arg( sRemoteTitle.replace( " :", "" ) )
				.arg( sLocalTitle.replace( " :", "" ) ) );
		// Add the button to the dialog button box
		wDlg.addButton( wUniquesBtn );
 
		// Connect the button pressed signal to the function that checks unique paths;
		// the function depends on the listview being defined, and so this is why we
		// defined the listview in the global scope
		connect( wUniquesBtn, "pressed()", setUniqueMappedDirs );
 
		// If the user cancelled
		if( !wDlg.exec() ){
			// Return an empty array
			return [];
		}
 
		// Get the checked remote items
		var aItems = getCheckedPathItems( g_wRemoteView );
		// Get the checked local items, too
		aItems = aItems.concat( getCheckedPathItems( g_wLocalView ) );
 
		// Return the checked items
		return aItems;
	};
 
	/*********************************************************************/
	// String : Prompt the user to choose an account
	function getUserAccount( aAccounts )
	{
		// Get the current author
		var oAuthor = App.getCurrentAuthor();
		// Get the name of the author
		var sAuthor = oAuthor.name
 
		// Create a basic dialog
		var wDlg = new DzBasicDialog();
		// Set the title
		wDlg.caption = g_sToolName;
 
		// Get the wrapped QWidget
		var oDlg = wDlg.getWidget();
		// Set the object name; used to store/retrieve unique dialog geometry
		oDlg.objectName = g_oStringHelper.stripSpaces( g_sToolName ) + "Account";
 
		// Create a button group
		var wAccountGrp = new DzVButtonGroup( wDlg );
		// Get the wrapped QWidget
		oAccountGrp = wAccountGrp.getWidget();
		// Set the object name; used for interactive lessons and inline help
		oAccountGrp.objectName = oDlg.objectName + "BGrp";
		// Set the title of the group
		wAccountGrp.title = text( "Choose an Account :" );
 
		// Declare working variables
		var wRadioBtn;
		var oRadioBtn;
		var sLabel;
 
		var sWhatsThis = String("<b>%1</b><br/><br/>%2.");
 
		// Iterate over the accounts
		for( var i = 0; i < aAccounts.length; i += 1 ){
			// Create a radio button
			wRadioBtn = new DzRadioButton( wAccountGrp );
			// Get the wrapped QWidget
			oRadioBtn = wRadioBtn.getWidget();
			// Set the object name; used for interactive lessons and inline help
			oRadioBtn.objectName = oDlg.objectName + "RBtn";
 
			// Get the URI decoded name of the account
			sLabel = decodeURIComponent( aAccounts[i] );
			// Strip the file extension from the label
			sLabel = sLabel.substring( 0, sLabel.lastIndexOf(".") );
			// Set the button text
			wRadioBtn.text = sLabel;
			// Set the tooltip text
			wRadioBtn.toolTip = text( "Select to modify the \"%1\" account" ).arg( sLabel );
			// Set the what's this text
			wRadioBtn.whatsThis = sWhatsThis.arg( sLabel ).arg( wRadioBtn.toolTip );
			// Add the button to the group
			wAccountGrp.addButton( wRadioBtn, i );
 
			// If the label is the current author
			if( sLabel == sAuthor ){
				// Set that button as the default
				wRadioBtn.checked = true;
			}
		}
 
		// If no default was set
		if( wAccountGrp.selected < 0 && wRadioBtn ){
			// Set the last button as the defalt
			wRadioBtn.checked = true;
		}
 
		// Add the group to the dialog
		wDlg.addWidget( wAccountGrp );
 
		// If the user doesn't cancel
		if( wDlg.exec() ){
			// Return the user's choice
			return aAccounts[ wAccountGrp.selected ];
		}
 
		// Return no choice made
		return "";
	};
 
	/*********************************************************************/
	// Object : Reads an Install Manager configuration *.ini and converts it to a JSON object
	function parseInstallManagerConfig( sConfigPath )
	{
		// Initialize the return object
		var oData = {};
 
		// Create a file object for the config
		var oConfigFile = new DzFile( sConfigPath );
		// Open the file for reading
		oConfigFile.open( DzFile.ReadOnly );
 
		// Read the lines from the file
		var aLines = oConfigFile.readLines();
 
		// Close the file
		oConfigFile.close();
 
		// Declare working variables
		var sLine = "";
		var aParts = [], aItems = [];
		var sSection = "", sKey = "", sValue = "";
		var nKey = 0, nValue = 0;
		var vValue = undefined;
		var oSection, oItem;
		var bInRange;
 
		// Iterate over the lines
		for( var i = 0; i < aLines.length; i += 1 ){
			// Get the 'current' line, trimmed of leading/trailing whitespace
			sLine = aLines[i].trim();
			// If the line is empty
			if( sLine.isEmpty() ){
				// Next!!
				continue;
			}
 
			// Split the line between key and value
			aParts = sLine.split( "=" );
			// Set the key to the first part
			sKey = aParts.shift();
			// Set the value to the rest
			sValue = aParts.join( "=" );
 
			// If the key starts and ends with square brackets
			if( sKey.startsWith( "[" ) && sKey.endsWith( "]" ) ){
				// If we have a section and a name
				if( oSection && !sSection.isEmpty() ){
					// Update the section
					oData[ sSection ] = oSection;
				}
 
				// Get the name of the section
				sSection = sKey.substring( 1, sKey.length - 1 );
				// If the section name is not valid
				if( g_aValidSectionNames.indexOf( sSection ) < 0 ){
					// Reset the section name
					sSection = "";
					// Record/report the problem
					print( "Invalid Section :", sKey );
				}
 
				// Create a section object
				oSection = {};
 
				// Next line!!
				continue;
			}
 
			// If we do not have a value
			if( sValue.isEmpty() ){
				// Next!!
				continue;
			}
 
			// If the key is multi-part;  index\key
			if( sKey.indexOf( "\\" ) > -1 ){
				// Split the key
				aParts = sKey.split( "\\" );
				// Set the index from the first part; adjust for zero-based
				nKey = Number( aParts.shift() ) - 1;
				// Set the key to the rest
				sKey = aParts.join( "\\" );
			// If the key is not multi-part
			} else {
				// Set the index to be outside the valid multi-part range
				nKey = -1;
			}
 
			// If the key name is not valid
			if( g_aValidKeyNames.indexOf( sKey ) == -1 ) {
				// Record/report the problem
				print( "Invalid Key :", sKey );
				// Next line!!
				continue;
			}
 
			// If the value is the string representation of true
			if( sValue == "true" ){
				// Set the value to record to the boolean
				vValue = true;
			// If the value is the string representation of false
			} else if( sValue == "false" ){
				// Set the value to record to the boolean
				vValue = false;
			// If the value is the string representation of a number
			} else if( Number( sValue ) == sValue ){
				// Convert the string to a number
				nValue = Number( sValue );
				// If the number is fractional
				if( nValue % 1 > 0 ){
					// Set the value to record to the floating point conversion
					vValue = parseFloat( sValue );
				// If the number is whole
				} else {
					// Set the value to record to the integer conversion
					vValue = parseInt( sValue );
				}
			// If the value is a string
			} else {
				// Set the value to record to the string
				vValue = sValue;
			}
 
			// If the name of the key is 'size'
			if( sKey == g_sSize ){
				// Pre-size an array for the items
				aItems = new Array( vValue );
			}
 
			// If the key index is within range; it's a multi-part key
			bInRange = (nKey > -1 && nKey < aItems.length);
			if( bInRange ){
				// Get the 'current' item
				oItem = aItems[ nKey ];
				// If the object is null; it will be the first time,
				// due to pre-sizing the array
				if( oItem == null ){
					// Create a new object
					oItem = {};
				}
			}
 
			// If we have an item and the index is in range; multi-part key
			if( oItem && bInRange ){
				// Set the item's key to the value
				oItem[ sKey ] = vValue;
				// Update the item in the array
				aItems[ nKey ] = oItem;
				// Update the section with the items
				oSection[ g_sItems ] = aItems;
			// If the key is anything other than 'size'
			} else if( sKey != g_sSize ){
				// Set the value of the key, in the 'current' section
				oSection[ sKey ] = vValue;
			}
		}
 
		// If the section is named
		if( !sSection.isEmpty() ){
			// Update the section on the data object
			oData[ sSection ] = oSection;
		}
 
		// Return the data object
		return oData;
	};
 
	/*********************************************************************/
	// void : Converts Install Manager configuration data (JSON) and writes to an *.ini file
	function writeInstallManagerConfig( sConfigPath, oConfigData )
	{
		// Create a file object for the config
		var oConfigFile = new DzFile( sConfigPath );
		// Open the file for writing
		oConfigFile.open( DzFile.WriteOnly );
 
		// Declare working variables
		var aKeyNames, aItemNames, aItems;
		var sSection, sKey, sValue;
		var oSection, oItem;
 
		// Get the section names from the object
		var aSectionNames = Object.keys( oConfigData );	
		// Iterate over the section names
		for( var i = 0; i < aSectionNames.length; i += 1  ){
			// If this is not the first iteration
			if( i > 0 ){
				// Write an empty separation line
				oConfigFile.writeLine( "" );
			}
 
			// Get the 'current' section name
			sSection = aSectionNames[ i ];
			// Write the section line
			oConfigFile.writeLine( String("[%1]").arg( sSection ) );
 
			// Get the 'current' section object
			oSection = oConfigData[ sSection ];
			// If we do not have an object
			if( !oSection ){
				// Next!!
				continue;
			}
 
			// Get the key names from it
			aKeyNames = Object.keys( oSection );
			// Iterate over the key names
			for( var j = 0; j < aKeyNames.length; j += 1  ){
				// Get the 'current' key name
				sKey = aKeyNames[ j ];
				// If the key is not named "items"
				if( sKey != g_sItems ){
					// Write the key and its value
					oConfigFile.writeLine( String("%1=%2")
						.arg( sKey ).arg( oSection[ sKey ] ) );
 
					// Next!!
					continue;
				}
 
				// Get the items array
				aItems = oSection[ sKey ];
				// Write the 'size' key and value for this section
				oConfigFile.writeLine( String("%1=%2")
					.arg( g_sSize ).arg( aItems.length ) );
				// Iterate over the items
				for( var k = 0; k < aItems.length; k += 1  ){
					// Get the 'current' item
					oItem = aItems[ k ];
					// Get the names of the keys
					aItemNames = Object.keys( oItem );
					// Iterate over the key names
					for( var m = 0; m < aItemNames.length; m += 1  ){
						// Get the 'current' key
						sKey = aItemNames[ m ];
						// Write the multi-part key and value for this item
						oConfigFile.writeLine( String("%1\\%2=%3")
							.arg( k + 1 ).arg( sKey ).arg( oItem[ sKey ] ) );
					}
				}
			}
		}
 
		// Close the file
		oConfigFile.close();
	};
 
	/*********************************************************************/
	// void : Do... whatever it is we do
	function begin()
	{
		// Get the appdata directory
		var oConfigDir = new DzDir( App.getAppDataPath() );
		// Up a directory; out of the application's directory
		oConfigDir.cdUp();
		// Change to the Install Manager directory
		oConfigDir.cd( "InstallManager" );
		// Change to the user accounts directory
		oConfigDir.cd( "UserAccounts" );
 
		// Declare working variable for the config path
		var sConfigPath = String("%1/Account.ini").arg( oConfigDir.path() );
 
		// Get all of the ini files in the directory
		var aConfigFiles = oConfigDir.entryList( "*.ini", DzDir.Files );
 
		// If there is only one
		if( aConfigFiles.length == 1 ){
			// Use it
			sConfigPath = String("%1/%2").arg( oConfigDir.path() ).arg( aConfigFiles[0] );
		// If there are multiple
		} else if( aConfigFiles.length > 1 ) {
			// Prompt the user to pick one
			sConfigPath = String("%1/%2").arg( oConfigDir.path() ).arg( getUserAccount( aConfigFiles ) );
		}
 
		// Create a file info object
		var oConfigFile = new DzFileInfo( sConfigPath );
		// If the file doesn't exist
		if( !oConfigFile.exists() ){
			// Inform the user
			MessageBox.information(
				text( "An account configuration file could not be found. Create an account " +
					"using Install Manager and then try again."),
				g_sToolName, text( "&OK" ) );
			// We cannot continue
			return;
		}
 
		// Convert the config file at the path to a JSON object
		var oConfigData = parseInstallManagerConfig( sConfigPath );
 
		// If the JSON object has install paths defined
		if( oConfigData.hasOwnProperty( g_sInstallPaths ) ){
			// Prompt the user to choose which ones they want mapped
			var aInstallPaths = getUserMappedDirs( decodeURIComponent( oConfigFile.baseName() ), oConfigData[ g_sInstallPaths ][ g_sItems ] );
 
			// If the user did not cancel (or choose none)
			if( aInstallPaths.length > 0 ){
				// Update the JSON object with the user's choices
				oConfigData[ g_sInstallPaths ][ g_sItems ] = aInstallPaths;
				// Write the config data to file
				writeInstallManagerConfig( sConfigPath, oConfigData );
			}
		}
	};
 
	/*********************************************************************/
	// Start doing stuff
	begin();
 
// Finalize the function and invoke
})();