diff --git a/xtrn/DDAreaChoosers/DDFileAreaChooser.js b/xtrn/DDAreaChoosers/DDFileAreaChooser.js index fc353d1f8e5ffc74eb952d44b7e67246f6f88000..1cb680793df6135ead4485831dee1892990c0428 100644 --- a/xtrn/DDAreaChoosers/DDFileAreaChooser.js +++ b/xtrn/DDAreaChoosers/DDFileAreaChooser.js @@ -52,6 +52,10 @@ * 2023-05-14 Eric Oulashin Version 1.35 * Refactored the configuration reading code. * Fix: Displays correct file counts in directories when using directory name collapsing + * 2023-07-21 Eric Oulashin Version 1.36 + * Fix for directory collapsing mode with the lightbar interface: It now exits + * when the user chooses their same file directory instead of continuing the + * menu input loop. */ // TODO: Failing silently when 1st argument is true @@ -92,8 +96,8 @@ if (system.version_num < 31400) } // Version & date variables -var DD_FILE_AREA_CHOOSER_VERSION = "1.35"; -var DD_FILE_AREA_CHOOSER_VER_DATE = "2023-05-14"; +var DD_FILE_AREA_CHOOSER_VERSION = "1.36"; +var DD_FILE_AREA_CHOOSER_VER_DATE = "2023-07-21"; // Keyboard input key codes var CTRL_H = "\x08"; @@ -1059,8 +1063,8 @@ function DDFileAreaChooser_SelectFileArea_Lightbar(pLevel, pLibIdx, pDirIdx, pCa // 3: Choose a subdirectory within a directory, for directory name collapsing else if ((level == 2) || (level == 3)) { - if (typeof(pLibIdx) != "number") - return; + // If there are no directories in the given library index, then see if there's a next library with + // directories (and wrap around) if (file_area.lib_list[pLibIdx].dir_list.length == 0) { console.clear("\x01n"); @@ -1386,27 +1390,36 @@ function DDFileAreaChooser_SelectFileArea_Lightbar(pLevel, pLibIdx, pDirIdx, pCa // enabled) var dirCodeBackup = bbs.curdir_code; var chosenFileDirIdx = this.SelectFileArea_Lightbar(level+1, chosenIdx, null, true); - if (chosenFileDirIdx > -1) - { - // Set the current file directory - if (this.useDirCollapsing) - bbs.curdir_code = this.lib_list[chosenIdx].dir_list[chosenFileDirIdx].code; - else - bbs.curdir_code = file_area.lib_list[chosenIdx].dir_list[chosenFileDirIdx].code; - continueOn = false; - } - else + // chosenFileDirIdx could actually be a boolean and could be false (returned + // when pLevel is 3 and the user chose a directory), so check its type and + // act accordingly. + var retValType = typeof(chosenFileDirIdx); + if (retValType === "boolean") + continueOn = chosenFileDirIdx; + else if (retValType === "number") { - // If the dir changed (probably at level 3 because directory - // collapsing is enabled), then exit here. - if (bbs.curdir_code != dirCodeBackup) + if (chosenFileDirIdx > -1) + { + // Set the current file directory + if (this.useDirCollapsing) + bbs.curdir_code = this.lib_list[chosenIdx].dir_list[chosenFileDirIdx].code; + else + bbs.curdir_code = file_area.lib_list[chosenIdx].dir_list[chosenFileDirIdx].code; continueOn = false; + } else { - // A file directory was not chosen, so we'll have to re-draw - // the header and key help line - displayListHdrLines(level, this, pLibIdx, pDirIdx); - this.WriteKeyHelpLine(); + // If the dir changed (probably at level 3 because directory + // collapsing is enabled), then exit here. + if (bbs.curdir_code != dirCodeBackup) + continueOn = false; + else + { + // A file directory was not chosen, so we'll have to re-draw + // the header and key help line + displayListHdrLines(level, this, pLibIdx, pDirIdx); + this.WriteKeyHelpLine(); + } } } } @@ -1427,10 +1440,12 @@ function DDFileAreaChooser_SelectFileArea_Lightbar(pLevel, pLibIdx, pDirIdx, pCa if (chosenSubdirIdx > -1) { // Set the current file directory - // TODO: This doesn't seem to be exiting, it's just - // going back to the library menu bbs.curdir_code = this.lib_list[pLibIdx].dir_list[chosenIdx].subdir_list[chosenSubdirIdx].code; continueOn = false; + // Return a false here so that after this function is called by itself when + // pLevel is 2, it will know that a sub-board has been chosen and will set + // continueOn to false so that the loop won't continue from there. + return false; } else { @@ -2826,3 +2841,35 @@ function attrCodeStr(pAttrCodeCharStr) return str; } +// Finds the index of a file library AFTER the given library index which contains +// directories. If there are none, this will return -1. +// +// Parameters: +// pLibIdx: An index of a file library; this function will start searching AFTER this one +// +// Return value: An index of a file library after the given group index that contains directories, +// or -1 if there are none +function findNextLibIdxWithDirs(pLibIdx) +{ + if (typeof(pLibIdx) !== "number") + return -1; + var nextLibIdx = -1; + //file_area.lib_list[pLibIdx].dir_list + if (pLibIdx < file_area.lib_list.length - 1) + { + for (var i = pLibIdx + 1; i < file_area.lib_list.length && nextLibIdx == -1; ++i) + { + if (file_area.lib_list[i].dir_list.length > 0) + nextLibIdx = i; + } + } + if (nextLibIdx == -1 && pLibIdx > 0) + { + for (var i = 0; i < pLibIdx && nextLibIdx == -1; ++i) + { + if (file_area.lib_list[i].dir_list.length > 0) + nextLibIdx = i; + } + } + return nextLibIdx; +} diff --git a/xtrn/DDAreaChoosers/readme.txt b/xtrn/DDAreaChoosers/readme.txt index 718134157c69c709d6449d59327e5313738bdfd4..cddc6cc77ca49781269e0bd40a00bc9716e940f7 100644 --- a/xtrn/DDAreaChoosers/readme.txt +++ b/xtrn/DDAreaChoosers/readme.txt @@ -1,6 +1,6 @@ Digital Distortion Area Choosers - Version 1.35 - Release date: 2023-05-14 + Version 1.36 + Release date: 2023-07-21 by diff --git a/xtrn/DDAreaChoosers/revision_history.txt b/xtrn/DDAreaChoosers/revision_history.txt index d8debf37974d4ae87138f7284b373b61863e1033..96ef17f9c3d2a71aacb04de8420a2014033be164 100644 --- a/xtrn/DDAreaChoosers/revision_history.txt +++ b/xtrn/DDAreaChoosers/revision_history.txt @@ -5,6 +5,11 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +1.36 2023-07-21 Message area chooser fix for not allowing to change sub- + board if the first group is empty. File area chooser fix: + When using directory collapsing mode with the lightbar + interface, it now exits when the user chooses their same + file directory instead of continuing the menu input loop. 1.35 2023-05-14 File area chooser fix: Displays correct file counts in directories when using directory name collapsing. Both: Internal refactor for the code that reads the diff --git a/xtrn/ddfilelister.js b/xtrn/ddfilelister.js deleted file mode 100644 index 7451fba24d5c83328374bf14c0a61d9b6aa70aa6..0000000000000000000000000000000000000000 --- a/xtrn/ddfilelister.js +++ /dev/null @@ -1,4212 +0,0 @@ -/* This is a file lister door for Synchronet. - * - * Author: Eric Oulashin (AKA Nightfox) - * BBS: Digital Distortion - * BBS address: digitaldistortionbbs.com (or digdist.synchro.net) - * - * Date Author Description - * 2022-01-17 Eric Oulashin Version 0.01 - * Started work on this script - * 2022-02-06 Eric Oulashin Version 2.00 - * Functionality implemented (for lightbar/ANSI terminal). - * Seems to work as expected. Releasing this version. - * I'm calling this version 2.00 because I had already - * released a file lister mod years ago (modding the stock - * Synchronet file list interface). - * 2022-02-07 Eric Oulashin Version 2.01 - * Fixed file description being undefined when viewing - * file info. Fixed command bar refreshing when pressing - * the hotkeys. Added an option to pause after viewing a - * file (defaults to true). - * 2022-02-13 Eric Oulashin Version 2.02 - * Things overall look good. Releasing this version. Added - * the ability to do searching via filespec, description, and - * new file search (started working on this 2022-02-08). - * 2022-02-27 Eric Oulashin Version 2.03 - * For terminals over 25 rows tall, the file info window will - * now be up to 45 rows tall. Also, fixed the display of the - * trailing blocks for the list header for wide terminals (over - * 80 columns). - * 2022-03-09 Eric Oulashin Version 2.04 - * Bug fix: Now successfully formats filenames without extensions - * when listing files. - * 2022-03-12 Eric Oulashin Version 2.05 - * Now makes use of the user's extended file description setting: - * If the user's extended file description setting is enabled, - * the lister will now show extended file descriptions on the - * main screen in a split format, with the lightbar file list - * on the left and the extended file description for the - * highlighted file on the right. Also, made the file info - * window taller for terminals within 25 lines high. - * I had started work on this on March 9, 2022. - * 2022-03-13 Eric Oulashin Version 2.05a - * Fix for "fileDesc is not defined" error when displaying - * the file description on the main screen. Also made a - * small refactor to the main screen refresh function. - * 2022-04-13 Eric Oulashin Version 2.06 - * When extended file descriptions are enabled, the file - * date is now shown with the file description on the last - * line. - * 2022-12-02 Eric Oulashin Version 2.07 - * In a file's extended description, added the number of times - * downloaded and date/time last downloaded. Also, fixed a bug - * where some descriptions were blank in the Frame object because - * of a leading normal attribute (the fix may be a kludge though). - * 2023-01-18 Eric Oulashin Version 2.08 - * When doing a file search in multiple directories, the file - * library & directory is now shown in the header as the user - * scrolls through the file list/search results. Also, - * used lfexpand() to ensure the extended description has - * CRLF endings, useful for splitting it into multiple lines properly. - * 2023-02-25 Eric Oulashin Version 2.09 - * Now supports being used as a loadable module for - * Scan Dirs and List Files - * 2023-02-27 Eric Oulashin Version 2.10 - * Now allows downloading a single selected file with the D key. - * Also, ddfilelister now checks whether the user has permission (based - * on ARS) to download before allowing adding files to their batch - * download queue (and downloading a single file as well). -*/ - -"use strict"; - -if (typeof(require) === "function") -{ - require("sbbsdefs.js", "K_UPPER"); - require('key_defs.js', 'KEY_UP'); - require("text.js", "Email"); // Text string definitions (referencing text.dat) - require("dd_lightbar_menu.js", "DDLightbarMenu"); - require("frame.js", "Frame"); - require("scrollbar.js", "ScrollBar"); - require("mouse_getkey.js", "mouse_getkey"); - require("attr_conv.js", "convertAttrsToSyncPerSysCfg"); -} -else -{ - load("sbbsdefs.js"); - load('key_defs.js'); - load("text.js"); // Text string definitions (referencing text.dat) - load("dd_lightbar_menu.js"); - load("frame.js"); - load("scrollbar.js"); - load("mouse_getkey.js"); - load("attr_conv.js"); -} - - -/* -Configured in SCFG->System->Loadable Modules: -Scan Dirs: User scans one or more directories for (e.g. new) files -List Files: User lists files within a file directory -View File Info: User views detailed information on files in a directory - -This addresses/fixes feature request #521 for Nightfox - -Will need to document the mode argument bit values on the wiki, but -it's the usual suspects: FL_* for scandirs and listfiles and FI_* for -fileinfo. The scandirs_mod will be passed an extra bool (0/1) arg that -indicates whether or not the user is scanning *all* directories. -*/ - - -// This script requires Synchronet version 3.19 or newer. -// If the Synchronet version is below the minimum, then exit. -if (system.version_num < 31900) -{ - if (user.is_sysop) - { - var message = "\x01n\x01h\x01y\x01i* Warning:\x01n\x01h\x01w Digital Distortion File Lister " - + "requires version \x01g3.19\x01w or\r\n" - + "newer of Synchronet. This BBS is using version \x01g" + system.version - + "\x01w.\x01n"; - console.crlf(); - console.print(message); - console.crlf(); - console.pause(); - } - exit(); -} - -// Lister version information -var LISTER_VERSION = "2.10"; -var LISTER_DATE = "2023-02-27"; - - -/////////////////////////////////////////////////////////////////////////////// -// Global variables - -var KEY_BACKSPACE = CTRL_H; - -// Block characters -var BLOCK1 = "\xB0"; // Dimmest block -var BLOCK2 = "\xB1"; -var BLOCK3 = "\xB2"; -var BLOCK4 = "\xDB"; // Brightest block -var THIN_RECTANGLE_LEFT = "\xDD"; -var THIN_RECTANGLE_RIGHT = "\xDE"; -var RIGHT_T_HDOUBLE_VSINGLE = "\xB5"; -var LEFT_T_HDOUBLE_VSINGLE = "\xcC6"; - -// For file sizes -//var BYTES_PER_TB = 1099511627776; // Seems to be too big for JS -var BYTES_PER_GB = 1073741824; -var BYTES_PER_MB = 1048576; -var BYTES_PER_KB = 1024; - -// File list column indexes (0-based). The end indexes are one past the last index. -// These defaults assume an 80-character wide terminal. -var gListIdxes = { - filenameStart: 0 -}; -// The end index of each column includes the trailing space so that -// highlight colors will highlight the whole field -gListIdxes.filenameEnd = gListIdxes.filenameStart + 13; -gListIdxes.fileSizeStart = gListIdxes.filenameEnd; -gListIdxes.fileSizeEnd = gListIdxes.fileSizeStart + 7; -gListIdxes.descriptionStart = gListIdxes.fileSizeEnd; -gListIdxes.descriptionEnd = console.screen_columns - 1; // Leave 1 character remaining on the screen -// Colors -var gColors = { - filename: "\x01n\x01b\x01h", - fileSize: "\x01n\x01m\x01h", - desc: "\x01n\x01w", - bkgHighlight: "\x01n\x01" + "4", - filenameHighlight: "\x01c\x01h", - fileSizeHighlight: "\x01c\x01h", - descHighlight: "\x01c\x01h", - fileTimestamp: "\x01g\x01h", - fileInfoWindowBorder: "\x01r", - fileInfoWindowTitle: "\x01g", - errorBoxBorder: "\x01g\x01h", - errorMessage: "\x01y\x01h", - successMessage: "\x01c", - - batchDLInfoWindowBorder: "\x01r", - batchDLInfoWindowTitle: "\x01g", - confirmFileActionWindowBorder: "\x01r", - confirmFileActionWindowWindowTitle: "\x01g", - - fileAreaMenuBorder: "\x01b", - fileNormalBkg: "\x01" + "4", - fileAreaNum: "\x01w", - fileAreaDesc: "\x01w", - fileAreaNumItems: "\x01w", - - fileAreaMenuHighlightBkg: "\x01" + "7", - fileAreaNumHighlight: "\x01b", - fileAreaDescHighlight: "\x01b", - fileAreaNumItemsHighlight: "\x01b" -}; - - -// Actions -var FILE_VIEW_INFO = 1; -var FILE_VIEW = 2; -var FILE_ADD_TO_BATCH_DL = 3; -var FILE_DOWNLOAD_SINGLE = 4; -var HELP = 5; -var QUIT = 6; -var FILE_MOVE = 7; // Sysop action -var FILE_DELETE = 8; // Sysop action - -// Search/list modes -var MODE_LIST_DIR = 1; -var MODE_SEARCH_FILENAME = 2; -var MODE_SEARCH_DESCRIPTION = 3; -var MODE_NEW_FILE_SEARCH = 4; - -// Sort orders (not included in FileBase.SORT) -var SORT_FL_ULTIME = 50; // Sort by upload time -var SORT_FL_DLTIME = 51; // Sort by download time - -// The searc/list mode for the current run -var gScriptMode = MODE_LIST_DIR; // Default -var gListBehavior = FL_NONE; // From sbbsdefs.js - -// The directory internal code to list -var gDirCode = bbs.curdir_code; - - - -// This will store the number of header lines that were displayed. This will control -// the starting row of the file list menu. -var gNumHeaderLinesDisplayed = 0; - -// The number of milliseconds to wait after displaying an error message -var gErrorMsgWaitMS = 1500; -// The upper-left position, width, & size of the error message box -var gErrorMsgBoxULX = 2; -var gErrorMsgBoxULY = 4; -var gErrorMsgBoxWidth = console.screen_columns - 2; -var gErrorMsgBoxHeight = 3; - -// Whether or not to pause after viewing a file -var gPauseAfterViewingFile = true; - -/////////////////////////////////////////////////////////////////////////////// -// Script execution code - -// The filename pattern to match -var gFilespec = "*"; - -// The sort order to use for the file list -var gFileSortOrder = FileBase.SORT.NATURAL; // Natural sort order, same as DATE_A (import date ascending) - -var gSearchVerbose = false; - -// When called as a lodable module, one of the options is to scan all dirs -var gScanAllDirs = false; - -// Read the configuration file and set the settings -readConfigFile(); - -// Parse command-line arguments (which sets program options) -parseArgs(argv); - -// If the user's terminal doesn't support ANSI, then just call the standard Synchronet -// file list function and exit now -if (!console.term_supports(USER_ANSI)) -{ - var exitCode = 0; - if (gScriptMode == MODE_SEARCH_FILENAME || gScriptMode == MODE_SEARCH_DESCRIPTION || gScriptMode == MODE_NEW_FILE_SEARCH) - bbs.scan_dirs(gListBehavior, gScanAllDirs); - else - exitCode = bbs.list_files(gDirCode, gFilespec, gListBehavior); - exit(exitCode); -} - -// This array will contain file metadata objects -var gFileList = []; - -// Populate the file list based on the script mode (list/search). -// It's important that this is called before createFileListMenu(), -// since this adjusts gListIdxes.filenameEnd based on the longest -// filename length and terminal width. -var listPopRetObj = populateFileList(gScriptMode); -if (listPopRetObj.exitNow) - exit(listPopRetObj.exitCode); - -// If there are no files, then say so and exit. -if (gFileList.length == 0) -{ - console.crlf(); - console.print("\x01n\x01c"); - if (gScriptMode == MODE_LIST_DIR) - { - if (gFilespec == "*" || gFilespec == "*.*") - console.print("There are no files in the directory."); - else - console.print("No files in the directory were found matching " + gFilespec); - } - else - console.print("No files were found."); - console.print("\x01n"); - console.crlf(); - console.pause(); - exit(0); -} - - -// Clear the screen and display the header lines -console.clear("\x01n"); -if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) - displayFileLibAndDirHeader(); -// Construct and display the menu/command bar at the bottom of the screen -var fileMenuBar = new DDFileMenuBar({ x: 1, y: console.screen_rows }); -fileMenuBar.writePromptLine(); -// Create the file list menu -var gFileListMenu = createFileListMenu(fileMenuBar.getAllActionKeysStr(true, true) + KEY_LEFT + KEY_RIGHT + KEY_DEL); -// In a loop, show the file list menu, allowing the user to scroll the file list, -// and respond to user input until the user decides to quit. -gFileListMenu.Draw({}); -// If using extended descriptions, write the first file's description on the screen -if (extendedDescEnabled()) - displayFileExtDescOnMainScreen(0); -var continueDoingFileList = true; -var drawFileListMenu = false; // For screen refresh optimization -while (continueDoingFileList) -{ - // Clear the menu's selected item indexes so it's 'fresh' for this round - for (var prop in gFileListMenu.selectedItemIndexes) - delete gFileListMenu.selectedItemIndexes[prop]; - var actionRetObj = null; - var currentActionVal = null; - var userChoice = gFileListMenu.GetVal(drawFileListMenu, gFileListMenu.selectedItemIndexes); - drawFileListMenu = false; // For screen refresh optimization - var lastUserInputUpper = gFileListMenu.lastUserInput != null ? gFileListMenu.lastUserInput.toUpperCase() : null; - if (lastUserInputUpper == null || lastUserInputUpper == "Q") - continueDoingFileList = false; - else if (lastUserInputUpper == KEY_LEFT) - fileMenuBar.decrementMenuItemAndRefresh(); - else if (lastUserInputUpper == KEY_RIGHT) - fileMenuBar.incrementMenuItemAndRefresh(); - else if (lastUserInputUpper == KEY_ENTER) - { - currentActionVal = fileMenuBar.getCurrentSelectedAction(); - fileMenuBar.setCurrentActionCode(currentActionVal); - actionRetObj = doAction_ANSI(currentActionVal, gFileList, gFileListMenu); - } - // Allow the delete key as a special key for sysops to delete the selected file(s). Also allow backspace - // due to some terminals returning backspace for delete. - else if (lastUserInputUpper == KEY_DEL || lastUserInputUpper == KEY_BACKSPACE) - { - if (user.is_sysop) - { - fileMenuBar.setCurrentActionCode(FILE_DELETE, true); - actionRetObj = doAction_ANSI(FILE_DELETE, gFileList, gFileListMenu); - currentActionVal = FILE_DELETE; - } - } - else - { - currentActionVal = fileMenuBar.getActionFromChar(lastUserInputUpper, false); - fileMenuBar.setCurrentActionCode(currentActionVal, true); - actionRetObj = doAction_ANSI(currentActionVal, gFileList, gFileListMenu); - } - // If an action was done (actionRetObj is not null), then look at actionRetObj and - // do what's needed. Note that quit (for the Q key) is already handled. - if (actionRetObj != null) - { - if (actionRetObj.exitNow) - continueDoingFileList = false; - else - { - if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) - { - if (actionRetObj.reDrawHeaderTextOnly) - { - console.print("\x01n"); - displayFileLibAndDirHeader(true); // Will move the cursor where it needs to be - } - else if (actionRetObj.reDrawListerHeader) - { - console.print("\x01n"); - console.gotoxy(1, 1); - displayFileLibAndDirHeader(); - } - } - if (actionRetObj.reDrawCmdBar) // Could call fileMenuBar.constructPromptText(); if needed - fileMenuBar.writePromptLine(); - var redrewPartOfFileListMenu = false; - // If we are to re-draw the main screen content, then - // enable the flag to draw the file list menu on the next - // GetVal(); also, if extended descriptions are being shown, - // write the current file's extended description too. - if (actionRetObj.reDrawMainScreenContent) - { - drawFileListMenu = true; - if (extendedDescEnabled()) - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); - } - else - { - // If there is partial redraw information available, then use it - // to re-draw that part of the main screen - if (actionRetObj.fileListPartialRedrawInfo != null) - { - drawFileListMenu = false; - var startX = actionRetObj.fileListPartialRedrawInfo.absStartX; - var startY = actionRetObj.fileListPartialRedrawInfo.absStartY; - var width = actionRetObj.fileListPartialRedrawInfo.width; - var height = actionRetObj.fileListPartialRedrawInfo.height; - refreshScreenMainContent(startX, startY, width, height, true); - actionRetObj.refreshedSelectedFilesAlready = true; - redrewPartOfFileListMenu = true; - } - else - { - // Partial screen re-draw information was not returned. - continueDoingFileList = actionRetObj.continueFileLister; - drawFileListMenu = actionRetObj.reDrawMainScreenContent; - // If displaying extended descriptions and the user deleted some files, then - // refresh the file description area to erase the delete confirmation text - if (extendedDescEnabled()/* && currentActionVal == FILE_DELETE*/) - { - if (actionRetObj.hasOwnProperty("filesDeleted") && actionRetObj.filesDeleted) - { - var numFiles = gFileListMenu.NumItems(); - if (numFiles > 0 && gFileListMenu.selectedItemIdx >= 0 && gFileListMenu.selectedItemIdx < numFiles) - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); - } - else - { - var firstLine = startY + gFileListMenu.pos.y; - var lastLine = console.screen_rows - 1; - var width = console.screen_columns - gFileListMenu.size.width - 1; - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx, firstLine, lastLine, width); - } - } - } - } - // Remove checkmarks from any selected files in the file menu. - // For efficiency, we'd probably only do this if not re-drawing the wohle - // menu, but that's not working for now. - if (!actionRetObj.refreshedSelectedFilesAlready && /*!drawFileListMenu &&*/ gFileListMenu.numSelectedItemIndexes() > 0) - { - var bottomItemIdx = gFileListMenu.GetBottomItemIdx(); - var redrawTopY = -1; - var redrawBottomY = -1; - if (actionRetObj.fileListPartialRedrawInfo != null) - { - redrawTopY = actionRetObj.fileListPartialRedrawInfo.absStartY; - redrawBottomY = actionRetObj.fileListPartialRedrawInfo.height + height - 1; - } - for (var idx in gFileListMenu.selectedItemIndexes) - { - var idxNum = +idx; - if (idxNum >= gFileListMenu.topItemIdx && idxNum <= bottomItemIdx) - { - var drawItem = true; - if (redrawTopY > -1 && redrawBottomY > redrawTopY) - { - var screenRowForItem = gFileListMenu.ScreenRowForItem(idxNum); - drawItem = (screenRowForItem < redrawTopY || screenRowForItem > redrawBottomY) - } - if (drawItem) - { - var isSelected = (idxNum == gFileListMenu.selectedItemIdx); - gFileListMenu.WriteItemAtItsLocation(idxNum, isSelected, false); - } - else - console.print("\x01n\r\nNot drawing idx " + idxNum + "\r\n\x01p"); - } - } - } - // If part of the file list menu was re-drawn (partially, not completely), move the cursor - // to the lower-right corner of the screen so that it's out of the way - if (redrewPartOfFileListMenu) - console.gotoxy(console.screen_columns-1, console.screen_rows); - } - } -} - - - - -/////////////////////////////////////////////////////////////////////////////// -// Functions: File actions - -// Performs a specified file action based on an action code. For the ANSI user interface. -// -// Parameters: -// pActionCode: A code specifying an action to do. Must be one of the global -// action codes. -// pFileList: The list of file metadata objects, as retrieved from the filebase -// pFileListMenu: The file list menu -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. -function doAction_ANSI(pActionCode, pFileList, pFileListMenu) -{ - if (typeof(pActionCode) !== "number") - return getDefaultActionRetObj(); - - var fileMetadata = pFileList[pFileListMenu.selectedItemIdx]; - - var retObj = null; - switch (pActionCode) - { - case FILE_VIEW_INFO: - retObj = showFileInfo_ANSI(fileMetadata); - break; - case FILE_VIEW: - retObj = viewFile_ANSI(fileMetadata); - break; - case FILE_ADD_TO_BATCH_DL: - if (userCanDownloadFromFileArea_ShowErrorIfNot(fileMetadata.dirCode)) - retObj = addSelectedFilesToBatchDLQueue_ANSI(fileMetadata, pFileList); - else - { - retObj = getDefaultActionRetObj(); - retObj.reDrawListerHeader = true; - retObj.reDrawHeaderTextOnly = false; - retObj.reDrawMainScreenContent = true; - retObj.reDrawCmdBar = true; - } - break; - case FILE_DOWNLOAD_SINGLE: - if (userCanDownloadFromFileArea_ShowErrorIfNot(fileMetadata.dirCode) && pFileListMenu.selectedItemIdx >= 0 && pFileListMenu.selectedItemIdx < pFileListMenu.NumItems()) - retObj = letUserDownloadSelectedFile_ANSI(fileMetadata); - else - { - retObj = getDefaultActionRetObj(); - retObj = getDefaultActionRetObj(); - retObj.reDrawListerHeader = true; - retObj.reDrawHeaderTextOnly = false; - retObj.reDrawMainScreenContent = true; - retObj.reDrawCmdBar = true; - } - break; - case HELP: - retObj = displayHelpScreen(); - break; - case QUIT: - retObj = getDefaultActionRetObj(); - retObj.continueFileLister = false; - break; - case FILE_MOVE: // Sysop action - if (user.is_sysop) - retObj = chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu); - break; - case FILE_DELETE: // Sysop action - if (user.is_sysop) - retObj = confirmAndRemoveFilesFromFilebase_ANSI(pFileList, pFileListMenu); - break; - } - - return retObj; -} - -// Returns an object for use for returning from performing a file action, -// with default values. -// -// Return value: An object with the following properties: -// continueFileLister: Boolean - Whether or not the file lister should continue, or exit -// reDrawMainScreenContent: Boolean - Whether or not to re-draw the main screen content -// (file list, and extended description area if applicable) -// reDrawListerHeader: Boolean - Whether or not to re-draw the header at the top of the screen -// reDrawHeaderTextOnly: Boolean - Whether or not to re-draw the header text only. This should -// take precedence over reDrawListerHeader. -// reDrawCmdBar: Boolean - Whether or not to re-draw the command bar at the bottom of the screen -// fileListPartialRedrawInfo: If part of the file list menu needs to be re-drawn, -// this will be an object that includes the following properties: -// startX: The starting X coordinate for where to re-draw -// startY: The starting Y coordinate for where to re-draw -// width: The width to re-draw -// height: The height to re-draw -// refreshedSelectedFilesAlready: Whether or not selected file checkmark items -// have already been refreshed (boolean) -// exitNow: Exit the file lister now (boolean) -// If no part of the file list menu needs to be re-drawn, this will be null. -function getDefaultActionRetObj() -{ - return { - continueFileLister: true, - reDrawMainScreenContent: false, - reDrawListerHeader: false, - reDrawHeaderTextOnly: false, - reDrawCmdBar: false, - fileListPartialRedrawInfo: null, - refreshedSelectedFilesAlready: false, - exitNow: false - }; -} - -// Shows extended information about a file to the user. -// -// Parameters: -// pFileMetadata: The file metadata object for the file to view information about -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. -function showFileInfo_ANSI(pFileMetadata) -{ - var retObj = getDefaultActionRetObj(); - - // The width of the frame to display the file info (including borders). This - // is declared early so that it can be used for string length adjustment. - //var frameWidth = pFileListMenu.size.width - 4; // TODO: Remove? - var frameWidth = console.screen_columns - 4; - - // pFileList[pFileListMenu.selectedItemIdx] has a file metadata object without - // extended information. Get a metadata object with extended information so we - // can display the extended description. - // The metadata object in pFileList should have a dirCode added by this script. - var dirCode = gDirCode; - if (pFileMetadata.hasOwnProperty("dirCode")) - dirCode = pFileMetadata.dirCode; - var fileMetadata = null; - if (extendedDescEnabled()) - fileMetadata = pFileMetadata; - else - fileMetadata = getFileInfoFromFilebase(dirCode, pFileMetadata.name, FileBase.DETAIL.EXTENDED); - // Build a string with the file information - // Make sure the displayed filename isn't too crazy long - var frameInnerWidth = frameWidth - 2; // Without borders - var adjustedFilename = shortenFilename(fileMetadata.name, frameInnerWidth, false); - var fileInfoStr = "\x01n\x01wFilename"; - if (adjustedFilename.length < fileMetadata.name.length) - fileInfoStr += " (shortened)"; - fileInfoStr += ":\r\n"; - fileInfoStr += gColors.filename + adjustedFilename + "\x01n\x01w\r\n"; - // Note: File size can also be retrieved by calling a FileBase's get_size(fileMetadata.name) - // TODO: Shouldn't need the max length here - fileInfoStr += "Size: " + gColors.fileSize + getFileSizeStr(fileMetadata.size, 99999) + "\x01n\x01w\r\n"; - fileInfoStr += "Timestamp: " + gColors.fileTimestamp + strftime("%Y-%m-%d %H:%M:%S", fileMetadata.time) + "\x01n\x01w\r\n"; - fileInfoStr += "\r\n"; - - // File library/directory information - var libIdx = file_area.dir[dirCode].lib_index; - var dirIdx = file_area.dir[dirCode].index; - var libDesc = file_area.lib_list[libIdx].description; - var dirDesc = file_area.dir[dirCode].description; - fileInfoStr += "\x01c\x01hLib\x01g: \x01n\x01c" + libDesc.substr(0, frameInnerWidth-5) + "\x01n\x01w\r\n"; - fileInfoStr += "\x01c\x01hDir\x01g: \x01n\x01c" + dirDesc.substr(0, frameInnerWidth-5) + "\x01n\x01w\r\n"; - fileInfoStr += "\r\n"; - - // fileMetadata should have extdDesc, but check just in case - var fileDesc = ""; - if (fileMetadata.hasOwnProperty("extdesc") && fileMetadata.extdesc.length > 0) - fileDesc = fileMetadata.extdesc; - else - fileDesc = fileMetadata.desc; - // It's possible for fileDesc to be undefined (due to extDesc or desc being undefined), - // so make sure it's a string. - // Also, if it's a string, reformat certain types of strings that don't look good in a - // Frame object - if (typeof(fileDesc) === "string") - { - // Check to see if it starts with a normal attribute and remove if so, - // since that seems to cause problems with displaying the description in a Frame object. This - // may be a kludge, and perhaps there's a better solution.. - fileDesc = fileDesc.replace(/^\x01[nN]/, ""); - // Fix line endings if necessary - fileDesc = lfexpand(fileDesc); - } - else - fileDesc = ""; - // This might be overkill, but just in case, convert any non-Synchronet - // attribute codes to Synchronet attribute codes in the description. - if (!fileMetadata.hasOwnProperty("attrsConverted")) - { - fileDesc = convertAttrsToSyncPerSysCfg(fileDesc); - fileMetadata.attrsConverted = true; - if (fileMetadata.hasOwnProperty("extdesc")) - fileMetadata.extdesc = fileDesc; - else - fileMetadata.desc = fileDesc; - } - - fileInfoStr += gColors.desc; - if (fileDesc.length > 0) - fileInfoStr += "Description:\r\n" + fileDesc; // Don't want to use strip_ctrl(fileDesc) - else - fileInfoStr += "No description available"; - fileInfoStr += "\r\n"; - // # of times downloaded and last downloaded date/time - var fieldFormatStr = "\r\n\x01n\x01c\x01h%s\x01g:\x01n\x01c %s"; - var timesDownloaded = fileMetadata.hasOwnProperty("times_downloaded") ? fileMetadata.times_downloaded : 0; - fileInfoStr += format(fieldFormatStr, "Times downloaded", timesDownloaded); - if (fileMetadata.hasOwnProperty("last_downloaded")) - fileInfoStr += format(fieldFormatStr, "Last downloaded", strftime("%Y-%m-%d %H:%M", fileMetadata.last_downloaded)); - // Some more fields for the sysop - if (user.is_sysop) - { - var sysopFields = [ "from", "cost", "added"]; - for (var sI = 0; sI < sysopFields.length; ++sI) - { - var prop = sysopFields[sI]; - if (fileMetadata.hasOwnProperty(prop)) - { - if (typeof(fileMetadata[prop]) === "string" && fileMetadata[prop].length == 0) - continue; - var propName = prop.charAt(0).toUpperCase() + prop.substr(1); - var infoValue = ""; - if (prop == "added") - infoValue = strftime("%Y-%m-%d %H:%M:%S", fileMetadata.added); - else - infoValue = fileMetadata[prop].toString().substr(0, frameInnerWidth); - fileInfoStr += format(fieldFormatStr, propName, infoValue); - fileInfoStr += "\x01n\x01w"; - } - } - } - fileInfoStr += "\x01n\x01w"; - - // Construct & draw a frame with the file information & do the input loop - // for the frame until the user closes the frame. - var frameUpperLeftX = 3; - var frameUpperLeftY = gNumHeaderLinesDisplayed + 3; - // Note: frameWidth is declared earlier - var frameHeight = console.screen_rows - 4 - frameUpperLeftY; - // If the user's console is more than 25 rows high, then make the info window - // taller so that its bottom row is 10 from the bottom, but only up to 45 rows tall. - if (console.screen_rows > 25) - { - var frameBottomRow = console.screen_rows - 4; - frameHeight = frameBottomRow - frameUpperLeftY + 1; - if (frameHeight > 45) - frameHeight = 45; - } - var frameTitle = "File Info"; - displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight, - gColors.fileInfoWindowBorder, frameTitle, - gColors.fileInfoWindowTitle, fileInfoStr); - - // Construct the file list redraw info. Note that the X and Y are relative - // to the file list menu, not absolute screen coordinates. - retObj.fileListPartialRedrawInfo = { - startX: frameUpperLeftX - gFileListMenu.pos.x + 1, // Relative to the file menu - startY: frameUpperLeftY - gFileListMenu.pos.y + 1, // Relative to the file menu - absStartX: frameUpperLeftX, - absStartY: frameUpperLeftY, - width: frameWidth, - height: frameHeight - }; - - return retObj; -} -// Splits a string on a given string and then re-combines the string with \r\n (carriage return & newline) -// at the end of each line -// -// Parameters: -// pStr: The string to split & recombine -// pSplitStr: The string to split the first string on -// -// Return value: The string split on pSplitStr and re-combined with \r\n at the end of each line -function splitStrAndCombineWithRN(pStr, pSplitStr) -{ - if (typeof(pStr) !== "string") - return ""; - if (typeof(pSplitStr) !== "string") - return pStr; - - var newStr = ""; - var strs = pStr.split(pSplitStr); - if (strs.length > 0) - { - for (var i = 0; i < strs.length; ++i) - newStr += strs[i] + "\r\n"; - newStr = newStr.replace(/\r\n$/, ""); - } - else - newStr = pStr; - return newStr; -} - -// Lets the user view a file. -// -// Parameters: -// pFileMetadata: The file metadata object for the file to view -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. -function viewFile_ANSI(pFileMetadata) -{ - var retObj = getDefaultActionRetObj(); - - // Open the filebase & get the fully pathed filename - var fullyPathedFilename = ""; - var filebase = new FileBase(pFileMetadata.dirCode); - if (filebase.open()) - { - fullyPathedFilename = filebase.get_path(pFileMetadata); - filebase.close(); - } - else - { - displayMsg("Failed to open the filebase!", true, true); - return retObj; - } - - // View the file - console.gotoxy(1, console.screen_rows); - console.print("\x01n"); - console.crlf(); - var successfullyViewed = bbs.view_file(fullyPathedFilename); - console.print("\x01n"); - if (gPauseAfterViewingFile || !successfullyViewed) - console.pause(); - - retObj.reDrawListerHeader = true; - retObj.reDrawMainScreenContent = true; - retObj.reDrawCmdBar = true; - return retObj; -} - -// Allows the user to add their selected file to their batch downloaded queue -// -// Parameters: -// pFileMetadata: The file metadata object for the file -// pFileList: The list of file metadata objects from the file directory -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. -function addSelectedFilesToBatchDLQueue_ANSI(pFileMetadata, pFileList) -{ - var retObj = getDefaultActionRetObj(); - if (!userCanDownloadFromFileArea_ShowErrorIfNot(pFileMetadata.dirCode)) - return retObj; - - // Confirm with the user to add the file(s) to their batch queue. If they don't want to, - // then just return now. - var filenames = []; - var metadataObjects = []; - if (gFileListMenu.numSelectedItemIndexes() > 0) - { - for (var idx in gFileListMenu.selectedItemIndexes) - { - var idxNum = +idx; - filenames.push(pFileList[idxNum].name); - metadataObjects.push(pFileList[idxNum]); - } - } - else - { - filenames.push(pFileMetadata.name); - metadataObjects.push(pFileMetadata); - } - // Note that confirmFileActionWithUser() will re-draw the parts of the file - // list menu that are necessary. - var addFilesConfirmed = confirmFileActionWithUser(filenames, "Batch DL add", false); - retObj.refreshedSelectedFilesAlready = true; - if (addFilesConfirmed) - { - var batchDLQueueStats = getUserDLQueueStats(); - var filenamesFailed = []; // To store filenames that failed to get added to the queue - var batchDLFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".dnload"; - var batchDLFile = new File(batchDLFilename); - if (batchDLFile.open(batchDLFile.exists ? "r+" : "w+")) - { - displayMsg("Adding file(s) to batch DL queue..", false, false); - for (var i = 0; i < metadataObjects.length; ++i) - { - // If the file isn't in the user's batch DL queue already, then add it. - var fileAlreadyInQueue = false; - for (var fIdx = 0; fIdx < batchDLQueueStats.filenames.length && !fileAlreadyInQueue; ++fIdx) - fileAlreadyInQueue = (batchDLQueueStats.filenames[fIdx].filename == metadataObjects[i].name); - if (!fileAlreadyInQueue) - { - var addToQueueSuccessful = true; - batchDLFile.writeln(""); - - // Add the required "dir" and "desc" properties to the user's batch download - // queue file. The section is the filename. Also, this script should add a - // dirCode property to each metadata object in the list. - addToQueueSuccessful = batchDLFile.iniSetValue(metadataObjects[i].name, "dir", metadataObjects[i].dirCode); - if (addToQueueSuccessful) - { - addToQueueSuccessful = batchDLFile.iniSetValue(metadataObjects[i].name, "desc", metadataObjects[i].desc); - // Update the batch DL queue stats object - ++(batchDLQueueStats.numFilesInQueue); - batchDLQueueStats.filenames.push({ filename: metadataObjects[i].name, desc: metadataObjects[i].desc }); - batchDLQueueStats.totalSize += +(metadataObjects[i].size); - batchDLQueueStats.totalCost += +(metadataObjects[i].cost); - } - - if (!addToQueueSuccessful) - filenamesFailed.push(metadataObjects[i].name); - } - } - - batchDLFile.close(); - } - - // Frame location & size for batch DL queue stats or filenames that failed - var frameUpperLeftX = gFileListMenu.pos.x + 2; - var frameUpperLeftY = gFileListMenu.pos.y + 2; - var frameWidth = console.screen_columns - 4; // Used to be gFileListMenu.size.width - 4; - var frameInnerWidth = frameWidth - 2; // Without borders - var frameHeight = 8; - - // If there were no failures, then show a success message & prompt the user if they - // want to download their batch queue. Otherwise, show the filenames that failed to - // get added. - if (filenamesFailed.length == 0) - { - displayMsg("Your batch DL queue was sucessfully updated", false, true); - // Prompt if the user wants to download their batch queue - if (bbs.batch_dnload_total > 0) - { - // Clear most of the screen area so the user has focus on the batch DL queue stats - var fullLineFormatStr = "%" + console.screen_columns + "s"; - var leftFormatStr = "%" + frameUpperLeftX + "s"; - var rightFormatStr = "%" + +(frameUpperLeftX+frameWidth-1) + "s"; - var lastFrameRow = frameUpperLeftY + frameHeight - 1; - var lastRow = console.screen_rows - 1; - console.print("\x01n"); - for (var screenRow = gNumHeaderLinesDisplayed+1; screenRow <= lastRow; ++screenRow) - { - console.gotoxy(1, screenRow); - if (screenRow < frameUpperLeftY || screenRow > lastFrameRow) - printf(fullLineFormatStr, ""); - else - { - printf(leftFormatStr, ""); - console.gotoxy(frameUpperLeftX+frameWidth, screenRow); - printf(rightFormatStr, ""); - } - } - - // Build a frame with batch DL queue stats and prompt the user if they want to - // download their batch DL queue - var frameTitle = "Download your batch queue (Y/N)?"; - // \x01cFiles: \x01h1 \x01n\x01c(\x01h100 \x01n\x01cMax) Credits: 0 Bytes: \x01h2,228,254 \x01n\x01c Time: 00:09:40 - // Note: The maximum number of allowed files in the batch download queue doesn't seem to - // be available to JavaScript. - var totalQueueSize = batchDLQueueStats.totalSize + pFileMetadata.size; - var totalQueueCost = batchDLQueueStats.totalCost + pFileMetadata.cost; - var queueStats = "\x01n\x01cFiles: \x01h" + batchDLQueueStats.numFilesInQueue + " \x01n\x01cCredits: \x01h" - + totalQueueCost + "\x01n\x01c Bytes: \x01h" + numWithCommas(totalQueueSize) + "\x01n\x01w\r\n"; - for (var i = 0; i < batchDLQueueStats.filenames.length; ++i) - { - queueStats += shortenFilename(batchDLQueueStats.filenames[i].filename, frameInnerWidth, false) + "\r\n"; - queueStats += batchDLQueueStats.filenames[i].desc.substr(0, frameInnerWidth) + "\r\n"; - if (i < batchDLQueueStats.filenames.length-1) - queueStats += "\r\n"; - } - var additionalQuitKeys = "yYnN"; - var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, - frameHeight, gColors.batchDLInfoWindowBorder, - frameTitle, gColors.batchDLInfoWindowTitle, - queueStats, additionalQuitKeys); - // The main screen content (file list & extended description if applicable) - // will need to be redrawn after this. - retObj.reDrawMainScreenContent = true; - // If the user chose to download their file queue, then send it to the user. - // And the lister headers will need to be re-drawn as well. - if (lastUserInput.toUpperCase() == "Y") - { - retObj.reDrawListerHeader = true; - retObj.reDrawCmdBar = true; - console.print("\x01n"); - console.gotoxy(1, console.screen_rows); - console.crlf(); - bbs.batch_download(); - // If the user is still online (chose not to hang up after transfer), - // then pause so that the user can see the batch download status - if (bbs.online > 0) - console.pause(); - } - } - } - else - { - eraseMsgBoxScreenArea(); - // Build a frame object to show the names of the files that failed to be added to the - // user's batch DL queue - var frameTitle = "Failed to add these files to batch DL queue"; - var fileListStr = "\x01n\x01w"; - for (var i = 0; i < filenamesFailed.length; ++i) - fileListStr += shortenFilename(filenamesFailed[i], frameInnerWidth, false) + "\r\n"; - var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, - frameHeight, gColors.batchDLInfoWindowBorder, - frameTitle, gColors.batchDLInfoWindowTitle, - fileListStr, ""); - // Add the file list redraw info. Note that the X and Y are relative - // to the file list menu, not absolute screen coordinates. - // To make the list refresh info to return to the main script loop - retObj.fileListPartialRedrawInfo = { - startX: 3, - startY: 3, - absStartX: gFileListMenu.pos.x + 3 - 1, // 1-based - absStartY: gFileListMenu.pos.y + 3 - 1, // 1-based - width: frameWidth + 1, - height: frameHeight - }; - } - } - - return retObj; -} -// Gets stats about the user's batch download queue. -// -// Return value: An object containing the following properties: -// numFilesInQueue: The number of files already in the queue -// totalSize: The total size of the files in the queue -// totalCost: The total cost of the files in the queue -// filenames: An array of objects, each containing the filename and -// descriptions (desc) of the files in the download queue -function getUserDLQueueStats() -{ - var retObj = { - numFilesInQueue: 0, - totalSize: 0, - totalCost: 0, - filenames: [] - }; - - var batchDLFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".dnload"; - var batchDLFile = new File(batchDLFilename); - if (batchDLFile.open(batchDLFile.exists ? "r+" : "w+")) - { - // See if a section exists for the filename - //File.iniGetAllObjects([name_property] [,prefix=none] [,lowercase=false] [,blanks=false]) - var allIniObjs = batchDLFile.iniGetAllObjects(); - console.attributes = "N"; - console.crlf(); - for (var i = 0; i < allIniObjs.length; ++i) - { - if (typeof(allIniObjs[i]) === "object") - { - ++(retObj.numFilesInQueue); - //allIniObjs[i].name - //allIniObjs[i].dir - //allIniObjs[i].desc - retObj.filenames.push({ filename: allIniObjs[i].name, desc: allIniObjs[i].desc }); - // dir is the internal directory code - if (allIniObjs[i].dir.length > 0) - { - var filebase = new FileBase(allIniObjs[i].dir); - if (filebase.open()) - { - var fileInfo = filebase.get(allIniObjs[i].name); - if (typeof(fileInfo) === "object") - { - retObj.totalSize += +(fileInfo.size); - retObj.totalCost += +(fileInfo.cost); - } - filebase.close(); - } - } - } - } - batchDLFile.close(); - } - - return retObj; -} - -// Lets the user download the currently selected file on the file list menu -// -// Parameters: -// pFileMetadata: The file metadata object for the file to download -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. -function letUserDownloadSelectedFile_ANSI(pFileMetadata) -{ - var retObj = getDefaultActionRetObj(); - console.attributes = "N"; - console.crlf(); - console.crlf(); - // If the user has the security level to download the file, let them do so - if (userCanDownloadFromFileArea_ShowErrorIfNot(pFileMetadata.dirCode)) - { - console.print("\x01cDownloading \x01h" + pFileMetadata.name + "\x01n"); - console.crlf(); - var selectedFilanmeFullPath = backslash(file_area.dir[pFileMetadata.dirCode].path) + pFileMetadata.name; - bbs.send_file(selectedFilanmeFullPath); - } - - retObj.reDrawListerHeader = true; - retObj.reDrawHeaderTextOnly = false; - retObj.reDrawMainScreenContent = true; - retObj.reDrawCmdBar = true; - - return retObj; -} - -// Displays the help screen. -function displayHelpScreen() -{ - var retObj = getDefaultActionRetObj(); - - console.clear("\x01n"); - // Display program information - displayTextWithLineBelow("Digital Distortion File Lister", true, "\x01n\x01c\x01h", "\x01k\x01h") - console.center("\x01n\x01cVersion \x01g" + LISTER_VERSION + " \x01w\x01h(\x01b" + LISTER_DATE + "\x01w)"); - console.crlf(); - - // If listing files in a directory, display information about the current file directory. - if (gScriptMode == MODE_LIST_DIR) - { - var libIdx = file_area.dir[gDirCode].lib_index; - var dirIdx = file_area.dir[gDirCode].index; - console.print("\x01n\x01cCurrent file library: \x01g" + file_area.lib_list[libIdx].description); - console.crlf(); - console.print("\x01cCurrent file directory: \x01g" + file_area.dir[gDirCode].description); - console.crlf(); - console.print("\x01cThere are \x01g" + file_area.dir[gDirCode].files + " \x01cfiles in this directory."); - } - else if (gScriptMode == MODE_SEARCH_FILENAME) - console.print("\x01n\x01cCurrently performing a filename search"); - else if (gScriptMode == MODE_SEARCH_DESCRIPTION) - console.print("\x01n\x01cCurrently performing a description search"); - else if (gScriptMode == MODE_NEW_FILE_SEARCH) - console.print("\x01n\x01cCurrently performing a new file search"); - console.crlf(); - console.crlf(); - - // Display information about the lister - var helpStr = "This lists files in your current file directory with a lightbar interface (for an ANSI terminal). "; - helpStr += "The file list can be navigated using the up & down arrow keys, PageUp, PageDown, Home, and End keys. " - helpStr += "The currently highlighted file in the menu is used by default for the various actions. For batch download " - helpStr += "selection, "; - if (user.is_sysop) - helpStr += "moving, and deleting, "; - helpStr += "you can select multiple files by using the spacebar. "; - helpStr += "There is also a command bar accross the bottom of the screen - You can select an action on the "; - helpStr += "action bar by using the left & right arrow keys and pressing enter to choose an action. Alternately, "; - helpStr += "you can press the first character of the action word to perform the action."; - helpStr += " Also, the following actions are available:"; - // Wrap the help string to the user's terminal width, and replace all instances of - // newlines with carriage return + newline, then display the help text. - helpStr = word_wrap(helpStr, console.screen_columns - 1).replace(/\n/g, "\r\n"); - console.print(helpStr); - // Display the commands available - var commandStrWidth = 8; - var printfStr = "\x01n\x01c\x01h%-" + commandStrWidth + "s\x01g: \x01n\x01c%s\r\n"; - printf(printfStr, "I", "Display extended file information"); - printf(printfStr, "V", "View the file"); - printf(printfStr, "B", "Flag the selected file(s) for batch download"); - printf(printfStr, "D", "Download the highlighted (selected) file"); - if (user.is_sysop) - { - printf(printfStr, "M", "Move the file(s) to another directory"); - printf(printfStr, "DEL", "Delete the file(s)"); - } - printf(printfStr, "?", "Show this help screen"); - printf(printfStr, "Q", "Quit back to the BBS"); - console.print("\x01n"); - console.crlf(); - //console.pause(); - - retObj.reDrawListerHeader = true; - retObj.reDrawMainScreenContent = true; - retObj.reDrawCmdBar = true; - return retObj; -} - -// Allows the user to move the selected file to another filebase. Only for sysops! -// -// Parameters: -// pFileList: The list of file metadata objects from the file directory -// pFileListMenu: The menu object for the file diretory -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. -function chooseFilebaseAndMoveFileToOtherFilebase_ANSI(pFileList, pFileListMenu) -{ - var retObj = getDefaultActionRetObj(); - - var fileMetadata = pFileList[pFileListMenu.selectedItemIdx]; - - // Confirm with the user to move the file(s). If they don't want to, - // then just return now. - var filenames = []; - if (pFileListMenu.numSelectedItemIndexes() > 0) - { - for (var idx in pFileListMenu.selectedItemIndexes) - filenames.push(pFileList[+idx].name); - } - else - filenames.push(fileMetadata.name); - // Note that confirmFileActionWithUser() will re-draw the parts of the file - // list menu that are necessary. - var moveFilesConfirmed = confirmFileActionWithUser(filenames, "Move", false); - retObj.refreshedSelectedFilesAlready = true; - if (!moveFilesConfirmed) - return retObj; - - - // Create a file library menu for the user to choose a file library (and then directory) - var fileLibMenu = createFileLibMenu(); - // For screen refresh purposes, construct the file list redraw info. Note that the X and Y are relative - // to the file list menu, not absolute screen coordinates. - var topYForRefresh = fileLibMenu.pos.y - 1; // - 1 because of the label above the menu - var fileListPartialRedrawInfo = { - startX: fileLibMenu.pos.x - pFileListMenu.pos.x + 1, - startY: topYForRefresh - pFileListMenu.pos.y + 1, - absStartX: fileLibMenu.pos.x, - absStartY: topYForRefresh, - width: fileLibMenu.size.width + 1, - height: fileLibMenu.size.height + 1 // + 1 because of the label above the menu - }; - console.gotoxy(fileLibMenu.pos.x, fileLibMenu.pos.y-1); - printf("\x01n\x01c\x01h|\x01n\x01c%-" + +(fileLibMenu.size.width-1) + "s\x01n", "Choose a destination area"); - // Prompt the user which directory to move the file to - var chosenDirCode = null; - var continueOn = true; - while (continueOn) - { - var chosenLibIdx = fileLibMenu.GetVal(); - if (typeof(chosenLibIdx) === "number") - { - // The file dir menu will be created at the same position & with the same size - // as the file library menu - var fileDirMenu = createFileDirMenu(chosenLibIdx); - chosenDirCode = fileDirMenu.GetVal(); - if (typeof(chosenDirCode) === "string") - { - if (chosenDirCode != fileMetadata.dirCode) - continueOn = false; - else - { - chosenDirCode = ""; - displayMsg("Can't move to the same directory", true); - } - } - } - else - continueOn = false; - } - // If the user chose a directory, then move the file there. - if (typeof(chosenDirCode) === "string" && chosenDirCode.length > 0) - { - // For logging - var libIdx = file_area.dir[chosenDirCode].lib_index; - var dirIdx = file_area.dir[chosenDirCode].index; - var libDesc = file_area.lib_list[libIdx].description; - var dirDesc = file_area.dir[chosenDirCode].description; - var destLibAndDirDesc = libDesc + " - " + dirDesc; - - // Build an array of file indexes and sort the array - var fileIndexes = []; - if (pFileListMenu.numSelectedItemIndexes() > 0) - { - for (var idx in pFileListMenu.selectedItemIndexes) - fileIndexes.push(+idx); - } - else - fileIndexes.push(+(pFileListMenu.selectedItemIdx)); - // Ensure the file indexes are sorted in numerical order - fileIndexes.sort(function(a, b) { return a - b}); - - // Go through the list of files and move each of them - var moveAllSucceeded = true; - for (var i = 0; i < fileIndexes.length; ++i) - { - var fileIdx = fileIndexes[i]; - // For logging - libIdx = file_area.dir[pFileList[fileIdx].dirCode].lib_index; - dirIdx = file_area.dir[pFileList[fileIdx].dirCode].index; - libDesc = file_area.lib_list[libIdx].description; - dirDesc = file_area.dir[pFileList[fileIdx].dirCode].description; - var srcLibAndDirDesc = libDesc + " - " + dirDesc; - - var moveRetObj = moveFileToOtherFilebase(pFileList[fileIdx], chosenDirCode); - var logMsg = ""; - var logLevel = LOG_INFO; - if (moveRetObj.moveSucceeded) - { - logMsg = "Digital Distotion File Lister: Successfully moved " + pFileList[fileIdx].name - + " from " + srcLibAndDirDesc + " to " + destLibAndDirDesc; - - // If we're listing files in the user's current directory, then remove - // the file info object from the file list array. Otherwise, update - // the metadata object in the list. - if (gScriptMode == MODE_LIST_DIR) - { - pFileList.splice(fileIdx, 1); - // Subtract 1 from the remaining indexes in the fileIndexes array - for (var j = i+1; j < fileIndexes.length; ++j) - fileIndexes[j] = fileIndexes[j] - 1; - // Have the file list menu set up its description width, colors, and format - // string again in case it no longer needs to use its scrollbar - pFileListMenu.SetItemWidthsColorsAndFormatStr(); - retObj.reDrawMainScreenContent = true; - } - else - { - // Note: getFileInfoFromFilebase() will add dirCode to the metadata object - var fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); - pFileList[fileIdx] = getFileInfoFromFilebase(chosenDirCode, pFileList[fileIdx].name, fileDetail); - } - } - else - { - moveAllSucceeded = false; - logLevel = LOG_ERR; - logMsg = "Digital Distotion File Lister: Failed to move " + pFileList[fileIdx].name - + " from " + srcLibAndDirDesc + " to " + destLibAndDirDesc; - } - log(logLevel, logMsg); - bbs.log_str(logMsg); - } - // Adjust the selected item index in the file list menu if necesary - if (pFileListMenu.NumItems() == 0) - pFileListMenu.selectedItemIdx = 0; - else if (pFileListMenu.selectedItemIdx >= pFileListMenu.NumItems() - 1) - pFileListMenu.selectedItemIdx = pFileListMenu.NumItems() - 1; - // If doing a search (not listing files in the user's current directory), then - // if all files were in the same directory, then we'll need to update the header - // lines at the top of the file list. If there's only one file in the list, - // the header lines will need to display the correct directory. Otherwise, - // set allSameDir to false so the header lines will now say "various". - // However, if not all files were in the same directory, check to see if they - // are now, and if so, we'll need to re-draw the header lines. - if (gScriptMode != MODE_LIST_DIR && typeof(pFileList.allSameDir) == "boolean") - { - if (pFileList.allSameDir) - { - if (pFileList.length > 1) - pFileList.allSameDir = false; - retObj.reDrawHeaderTextOnly = true; - } - else - { - pFileList.allSameDir = true; // Until we find it's not true - for (var fileListIdx = 1; fileListIdx < pFileList.length && pFileList.allSameDir; ++fileListIdx) - pFileList.allSameDir = (pFileList[fileListIdx].dirCode == pFileList[0].dirCode); - retObj.reDrawHeaderTextOnly = pFileList.allSameDir; - } - } - // Display a success/fail message - if (moveAllSucceeded) - { - var msg = "Successfully moved the file(s) to " + destLibAndDirDesc; - displayMsg(msg, false, true); - } - else - { - displayMsg("Failed to move the file(s)!", true, true); - } - // After moving the files, if there are no more files (in the directory or otherwise), - // say so and exit now. - if (gScriptMode == MODE_LIST_DIR && file_area.dir[gDirCode].files == 0) - { - displayMsg("There are no more files in the directory.", false); - retObj.exitNow = true; - } - else if (pFileList.length == 0) - { - displayMsg("There are no more files.", false); - retObj.exitNow = true; - } - } - - // If not exiting now, we'll want to re-draw part of the file list to erase the - // area chooser menu. - if (!retObj.exitNow) - retObj.fileListPartialRedrawInfo = fileListPartialRedrawInfo; - - return retObj; -} - -// Allows the user to remove the selected file(s) from the filebase. Only for sysops! -// -// Parameters: -// pFileList: The list of file metadata objects from the file directory -// pFileListMenu: The menu object for the file diretory -// -// Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. For this function, the object -// returned will have the following additional properties: -// filesDeleted: Boolean - Whether or not files were actually deleted (after -// confirmation) -function confirmAndRemoveFilesFromFilebase_ANSI(pFileList, pFileListMenu) -{ - var retObj = getDefaultActionRetObj(); - retObj.filesDeleted = false; - - // Confirm the action with the user. If the user confirms, then remove the file(s). - // If there are multiple selected files, then prompt to remove each of them. - // Otherwise, prompt for the one selected file. - var filenames = []; - if (pFileListMenu.numSelectedItemIndexes() > 0) - { - for (var idx in pFileListMenu.selectedItemIndexes) - filenames.push(pFileList[+idx].name); - } - else - filenames.push(pFileList[pFileListMenu.selectedItemIdx].name); - // Note that confirmFileActionWithUser() will re-draw the parts of the file list menu - // that are necessary. - var removeFilesConfirmed = confirmFileActionWithUser(filenames, "Remove", false); - retObj.refreshedSelectedFilesAlready = true; - if (removeFilesConfirmed) - { - retObj.filesDeleted = true; // Assume true even if some deletions may fail - - var fileIndexes = []; - if (pFileListMenu.numSelectedItemIndexes() > 0) - { - for (var idx in pFileListMenu.selectedItemIndexes) - fileIndexes.push(+idx); - } - else - fileIndexes.push(+(pFileListMenu.selectedItemIdx)); - // Ensure the file indexes are sorted in numerical order - fileIndexes.sort(function(a, b) { return a - b}); - - // Go through all the selected files and remove them. - // Note: Going through the list of indexes in reverse order so that - // removing each one from pFileList (gFileList) is simpler. - var removeAllSucceeded = true; - //for (var i = 0; i < fileIndexes.length; ++i) - for (var i = fileIndexes.length-1; i >= 0; --i) - { - var fileIdx = fileIndexes[i]; - if (typeof(pFileList[fileIdx]) === "undefined") - { - removeAllSucceeded = false; - continue; - } - // For logging - var libIdx = file_area.dir[pFileList[fileIdx].dirCode].lib_index; - var dirIdx = file_area.dir[pFileList[fileIdx].dirCode].index; - var libDesc = file_area.lib_list[libIdx].description; - var dirDesc = file_area.dir[pFileList[fileIdx].dirCode].description; - var libAndDirDesc = libDesc + " - " + dirDesc; - - // Open the filebase and remove the file - var removeFileSucceeded = false; - var numFilesRemaining = 0; - var filebase = new FileBase(pFileList[fileIdx].dirCode); - if (filebase.open()) - { - var filenameFullPath = filebase.get_path(pFileList[fileIdx].name); // For logging - try - { - removeFileSucceeded = filebase.remove(pFileList[fileIdx].name, true); - } - catch (error) - { - removeFileSucceeded = false; - // Make an entry in the BBS log that deleting the file failed - var logMsg = "ddfilelister: " + error; - log(LOG_ERR, logMsg); - bbs.log_str(logMsg); - } - // If the remove failed with deleting the file, then try without deleting the file - if (!removeFileSucceeded) - { - removeFileSucceeded = filebase.remove(pFileList[fileIdx].name, false); - if (removeFileSucceeded) - { - var logMsg = "ddfilelister: Removed " + filenameFullPath + " from the " - + "filebase but couldn't actually delete the file"; - log(LOG_INFO, logMsg); - bbs.log_str(logMsg); - } - } - if (removeFileSucceeded) - { - if (gScriptMode == MODE_LIST_DIR) - numFilesRemaining = filebase.files; - } - filebase.close(); - } - else - removeAllSucceeded = false; - - // Log a success/error message - var logMsg = ""; - var logLevel = LOG_INFO; - if (removeFileSucceeded) - { - logMsg = "Digital Distortion File Lister: Successfully removed " + pFileList[fileIdx].name - + " from " + libAndDirDesc + " (by " + user.alias + ")"; - // Remove the file info object from the file list array - pFileList.splice(fileIdx, 1); - // If we were going through the list in forward order, we'd have to - // subtract 1 from the remaining indexes: - /* - // Subtract 1 from the remaining indexes in the fileIndexes array - for (var j = i+1; j < fileIndexes.length; ++j) - fileIndexes[j] = fileIndexes[j] - 1; - */ - } - else - { - removeAllSucceeded = false; - logMsg = "Digital Distortion File Lister: Failed to remove " + pFileList[fileIdx].name - + " from " + libAndDirDesc + " (by " + user.alias + ")"; - logLevel = LOG_ERR; - } - log(logLevel, logMsg); - bbs.log_str(logMsg); - } - // Display a success/failure message - if (removeAllSucceeded) - displayMsg("Successfully removed the file(s)", false, true); - else - displayMsg("Failed to remove 1 or more files", true, true); - // Adjust the selected item index in the file list menu if necesary - if (pFileListMenu.NumItems() == 0) - pFileListMenu.selectedItemIdx = 0; - else if (pFileListMenu.selectedItemIdx >= pFileListMenu.NumItems() - 1) - pFileListMenu.selectedItemIdx = pFileListMenu.NumItems() - 1; - // If the file list still has files in it, have the menu redraw - // itself to refresh with the missing entry. Otherwise (no files left), - // say so and have the lister exit now. - numFilesRemaining = pFileList.length; - if (numFilesRemaining > 0) - { - // Have the file list menu set up its description width, colors, and format - // string again in case it no longer needs to use its scrollbar - pFileListMenu.SetItemWidthsColorsAndFormatStr(); - retObj.reDrawMainScreenContent = true; - // If all files were not in the same directory, then check to see if all - // remaining files are now. If so, we'll need to update the header lines - // at the top of the file list. - if (typeof(pFileList.allSameDir) == "boolean") - { - if (!pFileList.allSameDir) - { - pFileList.allSameDir = true; // Until we find it's not true - for (var i = 1; i < pFileList.length && pFileList.allSameDir; ++i) - pFileList.allSameDir = (pFileList[i].dirCode == pFileList[0].dirCode); - //retObj.reDrawListerHeader = pFileList.allSameDir; - retObj.reDrawHeaderTextOnly = pFileList.allSameDir; - } - } - // Also, if the file list menu can now show all its items on one - // page (not needing the scrollbar), set its top item index to 0. - if (pFileListMenu.CanShowAllItemsInWindow()) - pFileListMenu.topItemIdx = 0; - } - else - { - if (gScriptMode == MODE_LIST_DIR) - displayMsg("The directory now has no files.", false, true); - else - displayMsg("There are no more files to show.", false, true); - retObj.exitNow = true; - } - } - - return retObj; -} - -/////////////////////////////////////////////////////////////////////////////// -// DDFileMenuBar stuff - -function DDFileMenuBar(pPos) -{ - // Member functions - this.constructPromptText = DDFileMenuBar_constructPromptText; - this.getItemTextFromIdx = DDFileMenuBar_getItemTextFromIdx; - this.writePromptLine = DDFileMenuBar_writePromptLine; - this.refreshWithNewAction = DDFileMenuBar_refreshWithNewAction; - this.getDDFileMenuBarItemText = DDFileMenuBar_getDDFileMenuBarItemText; - this.incrementMenuItemAndRefresh = DDFileMenuBar_incrementMenuItemAndRefresh; - this.decrementMenuItemAndRefresh = DDFileMenuBar_decrementMenuItemAndRefresh; - this.getCurrentSelectedAction = DDFileMenuBar_getCurrentSelectedAction; - this.getActionFromChar = DDFileMenuBar_getActionFromChar; - this.setCurrentActionCode = DDFileMenuBar_setCurrentActionCode; - this.getAllActionKeysStr = DDFileMenuBar_getAllActionKeysStr; - - // Member variables - this.pos = { - x: 1, - y: 1 - }; - if (typeof(pPos) === "object" && pPos.hasOwnProperty("x") && pPos.hasOwnProperty("y") && typeof(pPos.x) === "number" && typeof(pPos.y) === "number") - { - if (pPos.x >= 1 && pPos.x <= console.screen_columns) - this.pos.x = pPos.x; - if (pPos.y >= 1 && pPos.y <= console.screen_rows) - this.pos.y = pPos.y; - } - - this.currentCommandIdx = 0; // The index of the current command for the menu array - this.lastCommandIdx = 0; // To keep track of the previous command index - - // Construct this.cmdArray: An array of the options - this.cmdArray = []; - this.cmdArray.push(new DDFileMenuBarItem("Info", 0, FILE_VIEW_INFO)); - this.cmdArray.push(new DDFileMenuBarItem("View", 0, FILE_VIEW)); - this.cmdArray.push(new DDFileMenuBarItem("Batch", 0, FILE_ADD_TO_BATCH_DL)); - this.cmdArray.push(new DDFileMenuBarItem("DL", 0, FILE_DOWNLOAD_SINGLE)); - if (user.is_sysop) - { - this.cmdArray.push(new DDFileMenuBarItem("Move", 0, FILE_MOVE)); - //this.cmdArray.push(new DDFileMenuBarItem("Del", 0, FILE_DELETE)); - this.cmdArray.push(new DDFileMenuBarItem("DEL", 0, FILE_DELETE, KEY_DEL)); - } - this.cmdArray.push(new DDFileMenuBarItem("?", 0, HELP)); - this.cmdArray.push(new DDFileMenuBarItem("Quit", 0, QUIT)); - - // Construct the prompt text (this must happen after this.cmdArray is built) - this.promptText = ""; - this.constructPromptText(); // this.promptText will be constructed here -} -// For the DDFileMenuBar class: Constructs the prompt text. This must be called after -// this.cmdArray is built. -// -// Return value: The number of additional solid blocks used to fill the whole screen row -function DDFileMenuBar_constructPromptText() -{ - var totalItemTextLen = 0; - for (var i = 0; i < this.cmdArray.length; ++i) - totalItemTextLen += this.cmdArray[i].itemText.length; - // The number of inner characters (without the outer solid blocks) is the total text - // length of all the items + 2 characters for each item except the last one - var numInnerChars = totalItemTextLen + (2 * (this.cmdArray.length-1)); - // The number of solid blocks: Subtracting 11 because there will be 5 block characters on each side, - // and subtract 1 extra so it doesn't fill the last character on the screen - var numSolidBlocks = console.screen_columns - numInnerChars - 11; - var numSolidBlocksPerSide = Math.floor(numSolidBlocks / 2); - // Build the prompt text: Start with the left blocks - this.promptText = "\x01n\x01w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4; - for (var i = 0; i < numSolidBlocksPerSide; ++i) - this.promptText += BLOCK4; - this.promptText += THIN_RECTANGLE_LEFT; - // Add the menu item text & block characters - var menuItemXPos = 6 + numSolidBlocksPerSide; // The X position of the start of item text for each item - for (var i = 0; i < this.cmdArray.length; ++i) - { - this.cmdArray[i].pos = menuItemXPos; - var numTrailingBlockChars = 0; - var selected = (i == this.currentCommandIdx); - var withTrailingBlock = false; - if (i < this.cmdArray.length-1) - { - withTrailingBlock = true; - numTrailingBlockChars = 2; - } - menuItemXPos += this.cmdArray[i].itemText.length + numTrailingBlockChars; - this.promptText += this.getDDFileMenuBarItemText(this.cmdArray[i].itemText, selected, withTrailingBlock); - } - // Add the right-side blocks - this.promptText += "\x01w" + THIN_RECTANGLE_RIGHT; - for (var i = 0; i < numSolidBlocksPerSide; ++i) - this.promptText += BLOCK4; - this.promptText += BLOCK3 + BLOCK2 + BLOCK1 + "\x01n"; -} -// For the DDFileMenuBar class: Gets the text for a prompt item based on its index -function DDFileMenuBar_getItemTextFromIdx(pIdx) -{ - if (typeof(pIdx) !== "number" || pIdx < 0 || pIdx >= this.cmdArray.length) - return ""; - return this.cmdArray[pIdx].itemText; -} -// For the DDFileMenuBar class: Writes the prompt text at the defined location -function DDFileMenuBar_writePromptLine() -{ - // Place the cursor at the defined location, then write the prompt text - console.gotoxy(this.pos.x, this.pos.y); - console.print(this.promptText); -} -// For the DDFileMenuBar class: Refreshes 2 items in the command bar text line -// -// Parameters: -// pCmdIdx: The index of the new/current command -function DDFileMenuBar_refreshWithNewAction(pCmdIdx) -{ - if (typeof(pCmdIdx) !== "number") - return; - if (pCmdIdx == this.currentCommandIdx) - return; - - // Refresh the prompt area for the previous index with regular colors - // Re-draw the last item text with regular colors - var itemText = this.getItemTextFromIdx(this.currentCommandIdx); - console.gotoxy(this.cmdArray[this.currentCommandIdx].pos, this.pos.y); - console.print("\x01n" + this.getDDFileMenuBarItemText(itemText, false, false)); - // Draw the new item text with selected colors - itemText = this.getItemTextFromIdx(pCmdIdx); - console.gotoxy(this.cmdArray[pCmdIdx].pos, this.pos.y); - console.print("\x01n" + this.getDDFileMenuBarItemText(itemText, true, false)); - console.gotoxy(this.pos.x+strip_ctrl(this.promptText).length-1, this.pos.y); - - this.lastCommandIdx = this.currentCommandIdx; - this.currentCommandIdx = pCmdIdx; - - // Re-construct the bar text to make sure it's up to date with the selected action - this.constructPromptText(); -} -// For the DDFileMenuBar class: Returns a string containing a piece of text for the -// menu bar text with its color attributes. -// -// Parameters: -// pText: The text for the item -// pSelected: Boolean - Whether or not the item is selected -// pWithTrailingBlock: Boolean - Whether or not to include the trailing block -// -// Return value: A string containing the item text for the action bar -function DDFileMenuBar_getDDFileMenuBarItemText(pText, pSelected, pWithTrailingBlock) -{ - if (typeof(pText) !== "string" || pText.length == 0) - return ""; - - var selected = (typeof(pSelected) === "boolean" ? pSelected : false); - var withTrailingBlock = (typeof(pWithTrailingBlock) === "boolean" ? pWithTrailingBlock : false); - - // Separate the first character from the rest of the text - var firstChar = pText.length > 0 ? pText.charAt(0) : ""; - var restOfText = pText.length > 1 ? pText.substr(1, pText.length - 1) : ""; - // Build the item text and return it - var itemText = "\x01n"; - if (selected) - itemText += "\x01" + "1\x01r\x01h" + firstChar + "\x01n\x01" + "1\x01k" + restOfText; - else - itemText += "\x01" + "6\x01c\x01h" + firstChar + "\x01n\x01" + "6\x01k" + restOfText; - itemText += "\x01n"; - if (withTrailingBlock) - itemText += "\x01w" + THIN_RECTANGLE_RIGHT + THIN_RECTANGLE_LEFT + "\x01n"; - return itemText; -} -// For the DDFileMenuBar class: Increments to the next menu item and refreshes the -// menu bar on the screen -function DDFileMenuBar_incrementMenuItemAndRefresh() -{ - var newCmdIdx = this.currentCommandIdx + 1; - if (newCmdIdx >= this.cmdArray.length) - newCmdIdx = 0; - // Will set this.currentCommandIdx - this.refreshWithNewAction(newCmdIdx); -} -// For the DDFileMenuBar class: Decrements to the previous menu item and refreshes the -// menu bar on the screen -function DDFileMenuBar_decrementMenuItemAndRefresh() -{ - var newCmdIdx = this.currentCommandIdx - 1; - if (newCmdIdx < 0) - newCmdIdx = this.cmdArray.length - 1; - // Will set this.currentCommandIdx - this.refreshWithNewAction(newCmdIdx); -} -// For the DDFileMenuBar class: Gets the return code for the currently selected action -function DDFileMenuBar_getCurrentSelectedAction() -{ - return this.cmdArray[this.currentCommandIdx].retCode; -} -// For the DDFileMenuBar class: Gets the return code matching a given character. -// If there is no match, this will return -1. -// -// Parameters: -// pChar: The character to match -// pCaseSensitive: Optional - Boolean - Whether or not to do a case-sensitive match. -// This defaults to false. -function DDFileMenuBar_getActionFromChar(pChar, pCaseSensitive) -{ - if (typeof(pChar) !== "string" || pChar.length == 0) - return -1; - - var caseSensitive = (typeof(pCaseSensitive) === "boolean" ? pCaseSensitive : false); - - var retCode = -1; - if (caseSensitive) - { - for (var i = 0; i < this.cmdArray.length && retCode == -1; ++i) - { - if (this.cmdArray[i].hotkeyOverride != null && typeof(this.cmdArray[i].hotkeyOverride) !== "undefined") - { - if (pChar == this.cmdArray[i].hotkeyOverride) - retCode = this.cmdArray[i].retCode; - } - else if (this.cmdArray[i].itemText.length > 0 && this.cmdArray[i].itemText.charAt(0) == pChar) - retCode = this.cmdArray[i].retCode; - } - } - else - { - // Not case sensitive - var charUpper = pChar.toUpperCase(); - for (var i = 0; i < this.cmdArray.length && retCode == -1; ++i) - { - if (this.cmdArray[i].hotkeyOverride != null && typeof(this.cmdArray[i].hotkeyOverride) !== "undefined") - { - if (pChar == this.cmdArray[i].hotkeyOverride) - retCode = this.cmdArray[i].retCode; - } - else if (this.cmdArray[i].itemText.length > 0 && this.cmdArray[i].itemText.charAt(0).toUpperCase() == charUpper) - retCode = this.cmdArray[i].retCode; - } - } - return retCode; -} -// For the DDFileMenuBar class: Sets the current command item in the menu bar based on its -// action code -// -// Parameters: -// pActionCode: The code of the action -// pRefreshOnScreen: Optional boolean - Whether or not to refresh the command bar on the screen. -// Defaults to false. -function DDFileMenuBar_setCurrentActionCode(pActionCode, pRefreshOnScreen) -{ - if (typeof(pActionCode) !== "number") - return; - - var refreshOnScreen = (typeof(pRefreshOnScreen) === "boolean" ? pRefreshOnScreen : false); - - for (var i = 0; i < this.cmdArray.length; ++i) - { - if (this.cmdArray[i].retCode == pActionCode) - { - if (refreshOnScreen) - this.refreshWithNewAction(i); - else - { - this.currentCommandIdx = i; - this.lastCommandIdx = i; - } - break; - } - } -} -// For the DDFileMenuBar: Gets all the action hotkeys as a string -// -// Parameters: -// pLowercase: Boolean - Whether or not to include letters as lowercase -// pUppercase: Boolean - Whether or not to include letters as uppercase -// -// Return value: All the action hotkeys as a string -function DDFileMenuBar_getAllActionKeysStr(pLowercase, pUppercase) -{ - var hotkeysStr = ""; - for (var i = 0; i < this.cmdArray.length; ++i) - { - if (this.cmdArray[i].itemText.length > 0) - hotkeysStr += this.cmdArray[i].itemText.charAt(0); - } - var hotkeysToReturn = ""; - if (pLowercase) - hotkeysToReturn += hotkeysStr.toLowerCase(); - if (pUppercase) - hotkeysToReturn += hotkeysStr.toUpperCase(); - return hotkeysToReturn; -} - -// Consctructor for an DDFileMenuBarItem -// -// Parameters: -// pItemText: The text of the item -// pPos: Horizontal (or vertical) starting location in the bar -// pRetCode: The item's return code -// pHotkeyOverride: Optional: A key to use for the action instead of the first character in pItemText -function DDFileMenuBarItem(pItemText, pPos, pRetCode, pHotkeyOverride) -{ - this.itemText = pItemText; - this.pos = pPos; - this.retCode = pRetCode; - this.hotkeyOverride = null; - if (pHotkeyOverride != null && typeof(pHotkeyOverride) !== "undefined") - this.hotkeyOverride = pHotkeyOverride; -} - - -/////////////////////////////////////////////////////////////////////////////// -// Helper functions - -// Gets file metadata & its file time from a filebase. -// -// Parameters: -// pDirCode: The internal code of the directory the file is in -// pFilename: The name of the file (without the full path) -// pDetail: The detail level of the metadata object to get - See FileBase.DETAIL in JS docs -// -// Return value: An object containing the following properties: -// fileMetadataObj: An object with extended file metadata from the filebase. -// This will have a dirCode property added. -// fileTime: The timestamp of the file -function getFileInfoFromFilebase(pDirCode, pFilename, pDetail) -{ - if (typeof(pDirCode) !== "string" || pDirCode.length == 0 || typeof(pFilename) !== "string" || pFilename.length == 0) - return null; - - var fileMetadataObj = null; - var filebase = new FileBase(pDirCode); - if (filebase.open()) - { - // Just in case the file has the full path, get just the filename from it. - var filename = file_getname(pFilename); - var fileDetail = (typeof(pDetail) === "number" ? pDetail : FileBase.DETAIL.NORM); - if (typeof(pDetail) === "number") - fileDetail = pDetail; - else - fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); - fileMetadataObj = filebase.get(filename, fileDetail); - fileMetadataObj.dirCode = pDirCode; - //fileMetadataObj.size = filebase.get_size(filename); - if (!fileMetadataObj.hasOwnProperty("time")) - fileMetadataObj.time = filebase.get_time(filename); - filebase.close(); - } - - return fileMetadataObj; -} - -// Moves a file from one filebase to another -// -// Parameters: -// pSrcFileMetadata: Metadata for the source file. This is assumed to contain 'normal' detail (not extended) -// pDestDirCode: The internal code of the destination filebase to move to the file to -// -// Return value: An object containing the following properties: -// moveSucceeded: Boolean - Whether or not the move succeeded -// failReason: If the move failed, this is a string that specifies why it failed -function moveFileToOtherFilebase(pSrcFileMetadata, pDestDirCode) -{ - var retObj = { - moveSucceeded: false, - failReason: "" - }; - - // pSrcFileMetadata is assumed to be a basic file metadata object, without extended - // information. Get a metadata object with maximum information so we have all - // metadata available. - var srcFilebase = new FileBase(pSrcFileMetadata.dirCode); - if (srcFilebase.open()) - { - var extdFileInfo = srcFilebase.get(pSrcFileMetadata, FileBase.DETAIL.MAX); - // Move the file over, remove it from the original filebase, and add it to the new filebase - var srcFilenameFull = srcFilebase.get_path(pSrcFileMetadata); - var destFilenameFull = file_area.dir[pDestDirCode].path + pSrcFileMetadata.name; - if (file_rename(srcFilenameFull, destFilenameFull)) - { - if (srcFilebase.remove(pSrcFileMetadata.name, false)) - { - // Add the file to the other directory - var destFilebase = new FileBase(pDestDirCode); - if (destFilebase.open()) - { - retObj.moveSucceeded = destFilebase.add(extdFileInfo); - destFilebase.close(); - } - else - { - retObj.failReason = "Failed to open the destination filebase"; - // Try to add the file back to the source filebase - var moveBackSucceeded = false; - if (file_rename(destFilenameFull, srcFilenameFull)) - moveBackSucceeded = srcFilebase.add(extdFileInfo); - if (!moveBackSucceeded) - retObj.failReason += " & moving the file back failed"; - } - } - else - retObj.failReason = "Failed to remove the file from the source directory"; - } - else - retObj.failReason = "Failed to move the file to the new filebase directory"; - - srcFilebase.close(); - } - else - { - var libIdx = file_area.dir[pSrcFileMetadata.dirCode].lib_index; - var dirIdx = file_area.dir[pSrcFileMetadata.dirCode].index; - var libDesc = file_area.lib_list[libIdx].description; - var dirDesc = file_area.dir[pSrcFileMetadata.dirCode].description; - retObj.failreason = "Failed to open filebase: " + libDesc + " - " + dirDesc; - } - - return retObj; -} - -// Counts the number of occurrences of a substring within a string -// -// Parameters: -// pStr: The string to count occurences in -// pSubstr: The string to look for within pStr -// -// Return value: The number of occurrences of pSubstr found in pStr -function countOccurrencesInStr(pStr, pSubstr) -{ - if (typeof(pStr) !== "string" || typeof(pSubstr) !== "string") return 0; - if (pStr.length == 0 || pSubstr.length == 0) return 0; - - var count = 0; - var strIdx = pStr.indexOf(pSubstr); - while (strIdx > -1 && strIdx < pStr.length) - { - ++count; - strIdx = pStr.indexOf(pSubstr, strIdx+1); - } - return count; -} - -// Constructs & displays a frame with a border around it, and performs a user input loop -// until the user quits out of the input loop. -// -// Parameters: -// pFrameX: The X coordinate of the upper-left corner of the frame (including border) -// pFrameY: The Y coordinate of the upper-left corner of the frame (including border) -// pFrameWidth: The width of the frame (including border) -// pFrameHeight: The height of the frame (including border) -// pBorderColor: The attribute codes for the border color -// pFrameTitle: The title (text) to use in the frame border -// pTitleColor: Optional string - The attribute codes for the color to use for the frame title -// pFrameContents: The contents to display in the frame -// pAdditionalQuitKeys: Optional - A string containing additional keys to quit the -// input loop. This is case-sensitive. -// -// Return value: The last keypress/input from the user -function displayBorderedFrameAndDoInputLoop(pFrameX, pFrameY, pFrameWidth, pFrameHeight, pBorderColor, pFrameTitle, pTitleColor, pFrameContents, pAdditionalQuitKeys) -{ - if (typeof(pFrameX) !== "number" || typeof(pFrameY) !== "number" || typeof(pFrameWidth) !== "number" || typeof(pFrameHeight) !== "number") - return; - - // Display the border for the frame - var keyHelpStr = "\x01n\x01c\x01hQ\x01b/\x01cEnter\x01b/\x01cESC\x01y: \x01gClose\x01b"; - var scrollLoopNavHelp = "\x01c\x01hUp\x01b/\x01cDn\x01b/\x01cHome\x01b/\x01cEnd\x01b/\x01cPgup\x01b/\x01cPgDn\x01y: \x01gNav"; - if (console.screen_columns >= 80) - keyHelpStr += ", " + scrollLoopNavHelp; - var borderColor = (typeof(pBorderColor) === "string" ? pBorderColor : "\x01r"); - drawBorder(pFrameX, pFrameY, pFrameWidth, pFrameHeight, borderColor, "double", pFrameTitle, pTitleColor, keyHelpStr); - - // Construct the frame window for the file info - // Create a Frame here with the full filename, extended description, etc. - var frameX = pFrameX + 1; - var frameY = pFrameY + 1; - var frameWidth = pFrameWidth - 2; - var frameHeight = pFrameHeight - 2; - var frameObj = new Frame(frameX, frameY, frameWidth, frameHeight, BG_BLACK); - frameObj.attr &=~ HIGH; - frameObj.v_scroll = true; - frameObj.h_scroll = false; - frameObj.scrollbars = true; - var scrollbarObj = new ScrollBar(frameObj, {bg: BG_BLACK, fg: LIGHTGRAY, orientation: "vertical", autohide: false}); - // Put the file info string in the frame window, then start the - // user input loop for the frame - frameObj.putmsg(pFrameContents, "\x01n"); - var lastUserInput = doFrameInputLoop(frameObj, scrollbarObj, pFrameContents, pAdditionalQuitKeys); - //infoFrame.bottom(); - - return lastUserInput; -} - -// Displays a Frame object and handles the input loop for navigation until -// the user presses Q, Enter, or ESC To quit the input loop -// -// Parameters: -// pFrame: The Frame object -// pScrollbar: The Scrollbar object for the Frame -// pFrameContentStr: The string content that was added to the Frame -// pAdditionalQuitKeys: Optional - A string containing additional keys to quit the -// input loop. This is case-sensitive. -// -// Return value: The last keypress/input from the user -function doFrameInputLoop(pFrame, pScrollbar, pFrameContentStr, pAdditionalQuitKeys) -{ - var checkAdditionalQuitKeys = (typeof(pAdditionalQuitKeys) === "string" && pAdditionalQuitKeys.length > 0); - - // Input loop for the frame to let the user scroll it - var frameContentTopYOffset = 0; - //var maxFrameYOffset = pFrameContentStr.split("\r\n").length - pFrame.height; - var maxFrameYOffset = countOccurrencesInStr(pFrameContentStr, "\r\n") - pFrame.height; - if (maxFrameYOffset < 0) maxFrameYOffset = 0; - var userInput = ""; - var continueOn = true; - do - { - pFrame.scrollTo(0, frameContentTopYOffset); - pFrame.invalidate(); - pScrollbar.cycle(); - pFrame.cycle(); - pFrame.draw(); - // Note: getKeyWithESCChars() is defined in dd_lightbar_menu.js. - userInput = getKeyWithESCChars(K_NOECHO|K_NOSPIN|K_NOCRLF, 30000).toUpperCase(); - if (userInput == KEY_UP) - { - if (frameContentTopYOffset > 0) - --frameContentTopYOffset; - } - else if (userInput == KEY_DOWN) - { - if (frameContentTopYOffset < maxFrameYOffset) - ++frameContentTopYOffset; - } - else if (userInput == KEY_PAGEUP) - { - frameContentTopYOffset -= pFrame.height; - if (frameContentTopYOffset < 0) - frameContentTopYOffset = 0; - } - else if (userInput == KEY_PAGEDN) - { - frameContentTopYOffset += pFrame.height; - if (frameContentTopYOffset > maxFrameYOffset) - frameContentTopYOffset = maxFrameYOffset; - } - else if (userInput == KEY_HOME) - frameContentTopYOffset = 0; - else if (userInput == KEY_END) - frameContentTopYOffset = maxFrameYOffset; - - // Check for whether to continue the input loop - continueOn = (userInput != "Q" && userInput != KEY_ENTER && userInput != KEY_ESC); - // If the additional quit keys does not contain the user's keypress, then continue - // the input loop. - // In other words, if the additional quit keys includes the user's keypress, then - // don't continue. - if (continueOn && checkAdditionalQuitKeys) - continueOn = (pAdditionalQuitKeys.indexOf(userInput) < 0); - } while (continueOn); - - return userInput; -} - -// Displays the header lines for showing above the file list -// -// Parameters: -// pTextOnly: Only draw the library & directory text (no decoration or other text). -// This is optional & defaults to false. -// pDirCodeOverride: Optional string: If this is valid, this will be used for the library & directory name -function displayFileLibAndDirHeader(pTextOnly, pDirCodeOverride) -{ - // If the behavior flags include no header, then just return immediately - if ((gListBehavior & FL_NO_HDR) == FL_NO_HDR) - return; - - var textOnly = (typeof(pTextOnly) === "boolean" ? pTextOnly : false); - - // Determine if this is the first time this function has been called. If so, - // we'll want to update gNumHeaderLinesDisplayed at the end. - var dispHdrFirstRun = false; - if (typeof(displayFileLibAndDirHeader.firstRun) == "undefined") - { - dispHdrFirstRun = true; - displayFileLibAndDirHeader.firstRun = true; - } - - // For the library & directory text to display, if we're just listing the user's - // current directory, use that. Otherwise, if all search results are in the same - // directory, then use a directory code from the file list. - var libIdx = 0; - var dirIdx = 0; - var libDesc = ""; - var dirDesc = ""; - var dirCode = ""; - if (gScriptMode == MODE_LIST_DIR) - dirCode = gDirCode; - else if (typeof(gFileList.allSameDir) === "boolean" && gFileList.allSameDir && gFileList.length > 0) - dirCode = gFileList[0].dirCode; - if (dirCode.length > 0) - { - libIdx = file_area.dir[dirCode].lib_index; - dirIdx = file_area.dir[dirCode].index; - libDesc = file_area.lib_list[libIdx].description; - dirDesc = file_area.dir[dirCode].description; - } - else if (typeof(pDirCodeOverride) === "string" && file_area.dir.hasOwnProperty(pDirCodeOverride)) - { - libIdx = file_area.dir[pDirCodeOverride].lib_index; - dirIdx = file_area.dir[pDirCodeOverride].index; - libDesc = file_area.lib_list[libIdx].description; - dirDesc = file_area.dir[pDirCodeOverride].description; - } - else - { - libIdx = -1; - dirIdx = -1; - libDesc = "Various"; - dirDesc = "Various"; - } - - var hdrTextWidth = console.screen_columns - 21; - var descWidth = hdrTextWidth - 11; - var libText = format("\x01cLib \x01w\x01h#\x01b%4d\x01c: \x01n\x01c%-" + descWidth + "s\x01n", +(libIdx+1), libDesc.substr(0, descWidth)); - var dirText = format("\x01cDir \x01w\x01h#\x01b%4d\x01c: \x01n\x01c%-" + descWidth + "s\x01n", +(dirIdx+1), dirDesc.substr(0, descWidth)); - - // Library line - if (textOnly) - { - console.gotoxy(6, 1); - console.print("\x01n" + libText); - console.gotoxy(6, 2); - console.print("\x01n" + dirText); - } - else - { - console.print("\x01n\x01w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4 + THIN_RECTANGLE_LEFT); - console.print(libText); - console.print("\x01w" + THIN_RECTANGLE_RIGHT + "\x01k\x01h" + BLOCK4 + "\x01n\x01w" + THIN_RECTANGLE_LEFT + - "\x01g\x01hDD File\x01n\x01w"); - console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); - console.crlf(); - // Directory line - console.print("\x01n\x01w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4 + THIN_RECTANGLE_LEFT); - console.print(dirText); - console.print("\x01w" + THIN_RECTANGLE_RIGHT + "\x01k\x01h" + BLOCK4 + "\x01n\x01w" + THIN_RECTANGLE_LEFT + - "\x01g\x01hLister \x01n\x01w"); - console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); - console.print("\x01n"); - - // List header - console.crlf(); - displayListHdrLine(false); - - if (dispHdrFirstRun) - { - gNumHeaderLinesDisplayed = 3; - gErrorMsgBoxULY = gNumHeaderLinesDisplayed; // Note: console.screen_rows is 1-based - } - } -} -// Displays the header line with the column headers for the file list -// -// Parameters: -// pMoveToLocationFirst: Boolean - Whether to move the cursor to the required location first. -function displayListHdrLine(pMoveToLocationFirst) -{ - if (pMoveToLocationFirst && console.term_supports(USER_ANSI)) - console.gotoxy(1, 3); - var filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; - var fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; - var descLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; - var formatStr = "\x01n\x01w\x01h%-" + filenameLen + "s %" + fileSizeLen + "s %-" - + +(descLen-7) + "s\x01n\x01w%5s\x01n"; - var listHdrEndText = THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1; - printf(formatStr, "Filename", "Size", "Description", listHdrEndText); -} - -// Creates the menu for displaying the file list -// -// Parameters: -// pQuitKeys: A string containing hotkeys to use as the menu's quit keys -// -// Return value: The DDLightbarMenu object for the file list in the file directory -function createFileListMenu(pQuitKeys) -{ - //DDLightbarMenu(pX, pY, pWidth, pHeight) - // Create the menu object. Place it below the header lines (which should have been written - // before this), and also leave 1 row at the bottom for the prompt line - var startRow = gNumHeaderLinesDisplayed > 0 ? gNumHeaderLinesDisplayed + 1 : 1; - // If we'll be displaying short (one-line) file descriptions, then use the whole width - // of the terminal (minus 1) for the menu width. But if the user has extended (multi-line) - // file descriptions enabled, then set the menu width only up through the file size, since - // the extended file description will be displayed to the right of the menu. - var menuWidth = console.screen_columns - 1; - if (extendedDescEnabled()) - menuWidth = gListIdxes.fileSizeEnd + 1; - var menuHeight = console.screen_rows - (startRow-1) - 1; - var fileListMenu = new DDLightbarMenu(1, startRow, menuWidth, menuHeight); - fileListMenu.scrollbarEnabled = true; - fileListMenu.borderEnabled = false; - fileListMenu.multiSelect = true; - fileListMenu.ampersandHotkeysInItems = false; - fileListMenu.wrapNavigation = false; - - fileListMenu.extdDescEnabled = extendedDescEnabled(); - - // Add additional keypresses for quitting the menu's input loop so we can - // respond to these keys. - if (typeof(pQuitKeys) === "string") - fileListMenu.AddAdditionalQuitKeys(pQuitKeys); - - // Define the menu's function to get the number of items. This must be done here - // in order for the menu's CanShowAllItemsInWindow() to work propertly. - fileListMenu.NumItems = function() { - return gFileList.length; - }; - - // Define a function for setting the description width, colors, and format string - // based on whether the menu's scrollbar will be used. - // - // Return value: Boolean: Whether or not the width changed - fileListMenu.SetItemWidthsColorsAndFormatStr = function() { - var widthChanged = false; - var oldDescriptionEnd = gListIdxes.descriptionEnd; - gListIdxes.descriptionEnd = console.screen_columns - 1; // Leave 1 character remaining on the screen - // If the file list menu will use its scrollbar, then reduce the description width by 1 - if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow()) - gListIdxes.descriptionEnd -= 1; - widthChanged = (gListIdxes.descriptionEnd != oldDescriptionEnd); - this.SetColors({ - itemColor: [{start: gListIdxes.filenameStart, end: gListIdxes.filenameEnd, attrs: gColors.filename}, - {start: gListIdxes.fileSizeStart, end: gListIdxes.fileSizeEnd, attrs: gColors.fileSize}, - {start: gListIdxes.descriptionStart, end: gListIdxes.descriptionEnd, attrs: gColors.desc}], - selectedItemColor: [{start: gListIdxes.filenameStart, end: gListIdxes.filenameEnd, attrs: gColors.bkgHighlight + gColors.filenameHighlight}, - {start: gListIdxes.fileSizeStart, end: gListIdxes.fileSizeEnd, attrs: gColors.bkgHighlight + gColors.fileSizeHighlight}, - {start: gListIdxes.descriptionStart, end: gListIdxes.descriptionEnd, attrs: gColors.bkgHighlight + gColors.descHighlight}] - }); - - this.filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; - this.fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; - this.shortDescLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; - // If extended descriptions are enabled, then we won't be writing a description here - if (this.extdDescEnabled) - this.fileFormatStr = "%-" + this.filenameLen + "s %" + this.fileSizeLen + "s"; - else - { - this.fileFormatStr = "%-" + this.filenameLen + "s %" + this.fileSizeLen - + "s %-" + this.shortDescLen + "s"; - } - return widthChanged; - }; - // Set up the menu's description width, colors, and format string - fileListMenu.SetItemWidthsColorsAndFormatStr(); - - fileListMenu.lastFileDirCode = ""; - // Define the menu function for getting an item - fileListMenu.GetItem = function(pIdx) { - // In here, 'this' refers to the fileListMenu object - // If doing a file search, then update the header with the file library & directory - // name of the currently selected file (instead of displaying "Various"). This seems - // like a bit of a hack, but it works. - var allSameDir = (typeof(gFileList.allSameDir) === "boolean" ? gFileList.allSameDir : false); - if (isDoingFileSearch() && !allSameDir && gFileList[pIdx].dirCode != this.lastFileDirCode) - { - if ((gListBehavior & FL_NO_HDR) != FL_NO_HDR) - { - var originalCurPos = console.getxy(); - displayFileLibAndDirHeader(true, gFileList[pIdx].dirCode); - console.gotoxy(originalCurPos); - } - } - this.lastFileDirCode = gFileList[pIdx].dirCode; - - var menuItemObj = this.MakeItemWithRetval(pIdx); - var filename = shortenFilename(gFileList[pIdx].name, this.filenameLen, true); - // FileBase.format_name() could also be called to format the filename for display: - /* - var filebase = new FileBase(gFileList[pIdx].dirCode); - if (filebase.open()) - { - filename = filebase.format_name(gFileList[pIdx].name, this.filenameLen, true); - filebase.close(); - } - */ - var desc = (typeof(gFileList[pIdx].desc) === "string" ? gFileList[pIdx].desc : ""); - menuItemObj.text = format(this.fileFormatStr, - filename, - getFileSizeStr(gFileList[pIdx].size, this.fileSizeLen), - desc.substr(0, this.shortDescLen)); - return menuItemObj; - } - - fileListMenu.selectedItemIndexes = {}; - fileListMenu.numSelectedItemIndexes = function() { - return Object.keys(this.selectedItemIndexes).length; - }; - - // If extended file descriptions are enabled, set the OnItemNav function for - // when the user navigates to a new item, to display the file description - // next to the file menu. - if (extendedDescEnabled()) - { - fileListMenu.OnItemNav = function(pOldItemIdx, pNewItemIdx) { - displayFileExtDescOnMainScreen(pNewItemIdx); - }; - } - - return fileListMenu; -} - -// Creates the menu for choosing a file library (for moving a message to another message area). -// The return value of the chosen item is the file library index. -// -// Return value: The DDLightbarMenu object for choosing a file library -function createFileLibMenu() -{ - // This probably shouldn't happen, but check to make sure there are file libraries - if (file_area.lib_list.length == 0) - { - console.crlf(); - console.print("\x01n\x01y\x01hThere are no file libraries available\x01n"); - console.crlf(); - console.pause(); - return; - } - - //DDLightbarMenu(pX, pY, pWidth, pHeight) - // Create the menu object - var startRow = gNumHeaderLinesDisplayed + 4; - var fileLibMenu = new DDLightbarMenu(5, startRow, console.screen_columns - 10, console.screen_rows - startRow - 5); - fileLibMenu.scrollbarEnabled = true; - fileLibMenu.borderEnabled = true; - fileLibMenu.multiSelect = false; - fileLibMenu.ampersandHotkeysInItems = false; - fileLibMenu.wrapNavigation = false; - - // Add additional keypresses for quitting the menu's input loop. - // Q: Quit - var additionalQuitKeys = "qQ"; - fileLibMenu.AddAdditionalQuitKeys(additionalQuitKeys); - - // Re-define the menu's function for getting the number of items. This is necessary - // here in order for the menu's CanShowAllItemsInWindow() to work properly. - fileLibMenu.NumItems = function() { - return file_area.lib_list.length; - }; - - // Construct a format string for the file libraries - var largestNumDirs = getLargestNumDirsWithinFileLibs(); - fileLibMenu.libNumLen = file_area.lib_list.length.toString().length; - fileLibMenu.numDirsLen = largestNumDirs.toString().length; - var menuInnerWidth = fileLibMenu.size.width - 2; // Menu width excluding borders - // If the scrollbar will be showing, reduce the inner width by 1 - if (fileLibMenu.scrollbarEnabled && !fileLibMenu.CanShowAllItemsInWindow()) - --menuInnerWidth; - // Allow 2 for spaces - fileLibMenu.libDescLen = menuInnerWidth - fileLibMenu.libNumLen - fileLibMenu.numDirsLen - 2; - fileLibMenu.libFormatStr = "%" + fileLibMenu.libNumLen + "d %-" + fileLibMenu.libDescLen + "s %" + fileLibMenu.numDirsLen + "d"; - - // Colors and their indexes - fileLibMenu.borderColor = gColors.fileAreaMenuBorder; - var libNumStart = 0; - var libNumEnd = fileLibMenu.libNumLen; - var descStart = libNumEnd; - var descEnd = descStart + fileLibMenu.libDescLen; - var numDirsStart = descEnd; - //var numDirsEnd = numDirsStart + fileLibMenu.numDirsLen; - fileLibMenu.SetColors({ - itemColor: [{start: libNumStart, end: libNumEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNum}, - {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaDesc}, - {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNumItems}], - selectedItemColor: [{start: libNumStart, end: libNumEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumHighlight}, - {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaDescHighlight}, - {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumItemsHighlight}] - }); - - fileLibMenu.topBorderText = "\x01y\x01hFile Libraries"; - // Define the menu function for getting an item - fileLibMenu.GetItem = function(pIdx) { - var menuItemObj = this.MakeItemWithRetval(pIdx); - menuItemObj.text = format(this.libFormatStr, - pIdx + 1,//file_area.lib_list[pIdx].number + 1, - file_area.lib_list[pIdx].description.substr(0, this.libDescLen), - file_area.lib_list[pIdx].dir_list.length); - return menuItemObj; - } - - return fileLibMenu; -} -// Helper for createFileLibMenu(): Returns the largest number of directories within the file libraries -function getLargestNumDirsWithinFileLibs() -{ - var largestNumDirs = 0; - for (var libIdx = 0; libIdx < file_area.lib_list.length; ++libIdx) - { - if (file_area.lib_list[libIdx].dir_list.length > largestNumDirs) - largestNumDirs = file_area.lib_list[libIdx].dir_list.length; - } - return largestNumDirs; -} - -// Creates the menu for choosing a file directory within a file library (for moving -// a message to another message area). -// The return value of the chosen item is the internal code for the file directory. -// -// Parameters: -// pLibIdx: The file directory index -// -// Return value: The DDLightbarMenu object for choosing a file directory within the -// file library at the given index -function createFileDirMenu(pLibIdx) -{ - if (typeof(pLibIdx) !== "number") - return null; - - var startRow = gNumHeaderLinesDisplayed + 4; - // Make sure there are directories in this library - if (file_area.lib_list[pLibIdx].dir_list.length == 0) - { - displayMsg("There are no directories in this file library", true, true); - return null; - } - - //DDLightbarMenu(pX, pY, pWidth, pHeight) - // Create the menu object - var fileDirMenu = new DDLightbarMenu(5, startRow, console.screen_columns - 10, console.screen_rows - startRow - 5); - fileDirMenu.scrollbarEnabled = true; - fileDirMenu.borderEnabled = true; - fileDirMenu.multiSelect = false; - fileDirMenu.ampersandHotkeysInItems = false; - fileDirMenu.wrapNavigation = false; - - // Add additional keypresses for quitting the menu's input loop. - // Q: Quit - var additionalQuitKeys = "qQ"; - fileDirMenu.AddAdditionalQuitKeys(additionalQuitKeys); - - // Re-define the menu's function for getting the number of items. This is necessary - // here in order for the menu's CanShowAllItemsInWindow() to work properly. - fileDirMenu.NumItems = function() { - return file_area.lib_list[this.libIdx].dir_list.length; - }; - - fileDirMenu.libIdx = pLibIdx; - // Construct a format string for the file libraries - var largestNumFiles = getLargestNumFilesInLibDirs(pLibIdx); - fileDirMenu.dirNumLen = file_area.lib_list[pLibIdx].dir_list.length.toString().length; - fileDirMenu.numFilesLen = largestNumFiles.toString().length; - var menuInnerWidth = fileDirMenu.size.width - 2; // Menu width excluding borders - // If the scrollbar will be showing, reduce the inner width by 1 - if (fileDirMenu.scrollbarEnabled && !fileDirMenu.CanShowAllItemsInWindow()) - --menuInnerWidth; - // Allow 2 for spaces - fileDirMenu.dirDescLen = menuInnerWidth - fileDirMenu.dirNumLen - fileDirMenu.numFilesLen - 2; - fileDirMenu.dirFormatStr = "%" + fileDirMenu.dirNumLen + "d %-" + fileDirMenu.dirDescLen + "s %" + fileDirMenu.numFilesLen + "d"; - - // Colors and their indexes - fileDirMenu.borderColor = gColors.fileAreaMenuBorder; - var dirNumStart = 0; - var dirNumEnd = fileDirMenu.dirNumLen; - var descStart = dirNumEnd; - var descEnd = descStart + fileDirMenu.dirDescLen; - var numDirsStart = descEnd; - //var numDirsEnd = numDirsStart + fileDirMenu.numDirsLen; - fileDirMenu.SetColors({ - itemColor: [{start: dirNumStart, end: dirNumEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNum}, - {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaDesc}, - {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileNormalBkg + gColors.fileAreaNumItems}], - selectedItemColor: [{start: dirNumStart, end: dirNumEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumHighlight}, - {start: descStart, end:descEnd, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaDescHighlight}, - {start: numDirsStart, end: -1, attrs: "\x01n" + gColors.fileAreaMenuHighlightBkg + gColors.fileAreaNumItemsHighlight}] - }); - - fileDirMenu.topBorderText = "\x01y\x01h" + ("File directories of " + file_area.lib_list[pLibIdx].description).substr(0, fileDirMenu.size.width-2); - // Define the menu function for ggetting an item - fileDirMenu.GetItem = function(pIdx) { - // Return the internal code for the directory for the item - var menuItemObj = this.MakeItemWithRetval(file_area.lib_list[this.libIdx].dir_list[pIdx].code); - menuItemObj.text = format(this.dirFormatStr, - pIdx + 1,//file_area.lib_list[this.libIdx].dir_list[pIdx].number + 1, - file_area.lib_list[this.libIdx].dir_list[pIdx].description.substr(0, this.dirDescLen), - file_area.lib_list[this.libIdx].dir_list[pIdx].files); - return menuItemObj; - } - - return fileDirMenu; -} - -// Returns the largest number of files in all directories in a file library -// -// Parameters: -// pLibIdx: The index of a file library -// -// Return value: The largest number of files of all the directories in the file library -function getLargestNumFilesInLibDirs(pLibIdx) -{ - var largestNumFiles = 0; - for (var dirIdx = 0; dirIdx < file_area.lib_list[pLibIdx].dir_list.length; ++dirIdx) - { - var numFilesInDir = file_area.lib_list[pLibIdx].dir_list[dirIdx].files; - if (numFilesInDir > largestNumFiles) - largestNumFiles = numFilesInDir; - } - return largestNumFiles; -} - -// Returns a formatted string representation of a file size. Tries -// to put a size designation at the end if possible. -// -// Parameters: -// pFileSize: The size of the file in bytes -// pMaxLen: Optional - The maximum length of the string -// -// Return value: A formatted string representation of the file size -function getFileSizeStr(pFileSize, pMaxLen) -{ - var fileSizeStr = "?"; - if (typeof(pFileSize) !== "number" || pFileSize < 0) - return fileSizeStr; - - // TODO: Improve - if (pFileSize >= BYTES_PER_GB) // Gigabytes - { - fileSizeStr = format("%.02fG", +(pFileSize / BYTES_PER_GB)); - if (typeof(pMaxLen) === "number" && pMaxLen > 0 && fileSizeStr.length > pMaxLen) - { - fileSizeStr = format("%.1fG", +(pFileSize / BYTES_PER_GB)); - if (fileSizeStr.length > pMaxLen) - { - // If there's a decimal point, then put the size designation after it - var dotIdx = fileSizeStr.lastIndexOf("."); - if (dotIdx > 0) - { - if (/\.$/.test(fileSizeStr)) - fileSizeStr = fileSizeStr.substr(0, dotIdx) + "G"; - else - fileSizeStr = fileSizeStr.substr(0, fileSizeStr.length-1) + "G"; - } - } - fileSizeStr = fileSizeStr.substr(0, pMaxLen); - } - } - else if (pFileSize >= BYTES_PER_MB) // Megabytes - { - fileSizeStr = format("%.02fM", +(pFileSize / BYTES_PER_MB)); - if (typeof(pMaxLen) === "number" && pMaxLen > 0 && fileSizeStr.length > pMaxLen) - { - fileSizeStr = format("%.1fM", +(pFileSize / BYTES_PER_MB)); - if (fileSizeStr.length > pMaxLen) - { - // If there's a decimal point, then put the size designation after it - var dotIdx = fileSizeStr.lastIndexOf("."); - if (dotIdx > 0) - { - if (/\.$/.test(fileSizeStr)) - fileSizeStr = fileSizeStr.substr(0, dotIdx) + "M"; - else - fileSizeStr = fileSizeStr.substr(0, fileSizeStr.length-1) + "M"; - } - } - fileSizeStr = fileSizeStr.substr(0, pMaxLen); - } - } - else if (pFileSize >= BYTES_PER_KB) // Kilobytes - { - fileSizeStr = format("%.02fK", +(pFileSize / BYTES_PER_KB)); - if (typeof(pMaxLen) === "number" && pMaxLen > 0 && fileSizeStr.length > pMaxLen) - { - fileSizeStr = format("%.1fK", +(pFileSize / BYTES_PER_KB)); - if (fileSizeStr.length > pMaxLen) - { - // If there's a decimal point, then put the size designation after it - var dotIdx = fileSizeStr.lastIndexOf("."); - if (dotIdx > 0) - { - if (/\.$/.test(fileSizeStr)) - fileSizeStr = fileSizeStr.substr(0, dotIdx) + "K"; - else - fileSizeStr = fileSizeStr.substr(0, fileSizeStr.length-1) + "K"; - } - } - fileSizeStr = fileSizeStr.substr(0, pMaxLen); - } - } - else - { - fileSizeStr = pFileSize.toString(); - if (typeof(pMaxLen) === "number" && pMaxLen > 0 && fileSizeStr.length > pMaxLen) - fileSizeStr = fileSizeStr.substr(0, pMaxLen); - } - - return fileSizeStr; -} - -// Displays some text with a solid horizontal line on the next line. -// -// Parameters: -// pText: The text to display -// pCenter: Whether or not to center the text. Optional; defaults -// to false. -// pTextColor: The color to use for the text. Optional; by default, -// normal white will be used. -// pLineColor: The color to use for the line underneath the text. -// Optional; by default, bright black will be used. -function displayTextWithLineBelow(pText, pCenter, pTextColor, pLineColor) -{ - var centerText = (typeof(pCenter) == "boolean" ? pCenter : false); - var textColor = (typeof(pTextColor) == "string" ? pTextColor : "\x01n\x01w"); - var lineColor = (typeof(pLineColor) == "string" ? pLineColor : "\x01n\x01k\x01h"); - - // Output the text and a solid line on the next line. - if (centerText) - { - console.center(textColor + pText); - var solidLine = ""; - var textLength = console.strlen(pText); - for (var i = 0; i < textLength; ++i) - solidLine += HORIZONTAL_SINGLE; - console.center(lineColor + solidLine); - } - else - { - console.print(textColor + pText); - console.crlf(); - console.print(lineColor); - var textLength = console.strlen(pText); - for (var i = 0; i < textLength; ++i) - console.print(HORIZONTAL_SINGLE); - console.crlf(); - } -} - -// Returns a string for a number with commas added every 3 places -// -// Parameters: -// pNum: The number to format -// -// Return value: A string with the number formatted with commas every 3 places -function numWithCommas(pNum) -{ - var numStr = ""; - if (typeof(pNum) === "number") - numStr = pNum.toString(); - else if (typeof(pNum) === "string") - numStr = pNum; - else - return ""; - - // Check for a decimal point in the number - var afterDotSuffix = ""; - var dotIdx = numStr.lastIndexOf("."); - if (dotIdx > -1) - { - afterDotSuffix = numStr.substr(dotIdx+1); - numStr = numStr.substr(0, dotIdx); - } - // First, build an array containing sections of the number containing - // 3 digits each (the last may contain less than 3) - var numParts = []; - var i = numStr.length - 1; - var continueOn = true; - while (continueOn/*i >= 0*/) - { - if (i >= 3) - { - numParts.push(numStr.substr(i-2, 3)); - i -= 3; - } - else - { - numParts.push(numStr.substr(0, i+1)); - i -= i; - continueOn = false; - } - } - // Reverse the array so the sections of digits are in forward order - numParts.reverse(); - // Re-build the number string with commas - numStr = ""; - for (var i = 0; i < numParts.length; ++i) - numStr += numParts[i] + ","; - if (/,$/.test(numStr)) - numStr = numStr.substr(0, numStr.length-1); - // Append back the value after the decimal place if there was one - if (afterDotSuffix.length > 0) - numStr += "." + afterDotSuffix; - return numStr; -} - -// Displays a set of messages at the status location on the screen, along with a border. Then -// refreshes the area of the screen to erase the message box. -// -// Parameters: -// pMsgArray: An array containing the messages to display -// pIsError: Boolean - Whether or not the messages are an error -// pWaitAndErase: Optional boolean - Whether or not to automatically wait a moment and then -// erase the message box after drawing. Defaults to true. -function displayMsgs(pMsgArray, pIsError, pWaitAndErase) -{ - if (typeof(pMsgArray) !== "object" || pMsgArray.length == 0) - return; - - var waitAndErase = (typeof(pWaitAndErase) === "boolean" ? pWaitAndErase : true); - - // Draw the box border, then write the messages - var title = pIsError ? "Error" : "Message"; - var titleColor = pIsError ? gColors.errorMessage : gColors.successMessage; - drawBorder(gErrorMsgBoxULX, gErrorMsgBoxULY, gErrorMsgBoxWidth, gErrorMsgBoxHeight, - gColors.errorBoxBorder, "single", title, titleColor, ""); - var msgColor = "\x01n" + (pIsError ? gColors.errorMessage : gColors.successMessage); - var innerWidth = gErrorMsgBoxWidth - 2; - var msgFormatStr = msgColor + "%-" + innerWidth + "s\x01n"; - for (var i = 0; i < pMsgArray.length; ++i) - { - console.gotoxy(gErrorMsgBoxULX+1, gErrorMsgBoxULY+1); - printf(msgFormatStr, pMsgArray[i].substr(0, innerWidth)); - if (waitAndErase) - { - // Wait for the error wait duration - mswait(gErrorMsgWaitMS); - } - } - if (waitAndErase) - eraseMsgBoxScreenArea(); -} -function displayMsg(pMsg, pIsError, pWaitAndErase) -{ - if (typeof(pMsg) !== "string") - return; - displayMsgs([ pMsg ], pIsError, pWaitAndErase); -} -// Erases the message box screen area by re-drawing the necessary components -function eraseMsgBoxScreenArea() -{ - // Refresh the list header line and have the file list menu refresh itself over - // the error message window - displayListHdrLine(true); - // This used to call gFileListMenu.DrawPartialAbs - refreshScreenMainContent(gErrorMsgBoxULX, gErrorMsgBoxULY+1, gErrorMsgBoxWidth, gErrorMsgBoxHeight-2); -} - -// Draws a border -// -// Parameters: -// pX: The X location of the upper left corner -// pY: The Y location of the upper left corner -// pWidth: The width of the box -// pHeight: The height of the box -// pColor: A string containing color/attribute codes for the border characters -// pLineStyle: A string specifying the border character style, either "single" or "double" -// pTitle: Optional - A string specifying title text for the top border -// pTitleColor: Optional - Attribute codes for the color to use for the title text -// pBottomBorderText: Optional - A string specifying text to include in the bottom border -function drawBorder(pX, pY, pWidth, pHeight, pColor, pLineStyle, pTitle, pTitleColor, pBottomBorderText) -{ - if (typeof(pX) !== "number" || typeof(pY) !== "number" || typeof(pWidth) !== "number" || typeof(pHeight) !== "number") - return; - if (typeof(pColor) !== "string") - return; - - var borderChars = { - UL: UPPER_LEFT_SINGLE, - UR: UPPER_RIGHT_SINGLE, - LL: LOWER_LEFT_SINGLE, - LR: LOWER_RIGHT_SINGLE, - preText: RIGHT_T_SINGLE, - postText: LEFT_T_SINGLE, - horiz: HORIZONTAL_SINGLE, - vert: VERTICAL_SINGLE - }; - if (typeof(pLineStyle) === "string" && pLineStyle.toUpperCase() == "DOUBLE") - { - borderChars.UL = UPPER_LEFT_DOUBLE; - borderChars.UR = UPPER_RIGHT_DOUBLE; - borderChars.LL = LOWER_LEFT_DOUBLE; - borderChars.LR = LOWER_RIGHT_DOUBLE; - borderChars.preText = RIGHT_T_DOUBLE; - borderChars.postText = LEFT_T_DOUBLE - borderChars.horiz = HORIZONTAL_DOUBLE; - borderChars.vert = VERTICAL_DOUBLE; - } - - // Top border - console.gotoxy(pX, pY); - console.print("\x01n" + pColor); - console.print(borderChars.UL); - var innerWidth = pWidth - 2; - // Include the title text in the top border, if there is any specified - var titleTextWithoutAttrs = strip_ctrl(pTitle); // Possibly used twice, so only call strip_ctrl() once - if (typeof(pTitle) === "string" && titleTextWithoutAttrs.length > 0) - { - var titleLen = strip_ctrl(pTitle).length; - if (titleLen > pWidth - 4) - titleLen = pWidth - 4; - innerWidth -= titleLen; - innerWidth -= 2; // ?? Correctional - // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js - var titleText = pTitle; - if (typeof(pTitleColor) === "string") - titleText = "\x01n" + pTitleColor + titleTextWithoutAttrs; - console.print(borderChars.preText + "\x01n" + substrWithAttrCodes(titleText, 0, titleLen) + - "\x01n" + pColor + borderChars.postText); - if (innerWidth > 0) - console.print(pColor); - } - for (var i = 0; i < innerWidth; ++i) - console.print(borderChars.horiz); - console.print(borderChars.UR); - // Side borders - var rightCol = pX + pWidth - 1; - var endScreenRow = pY + pHeight - 1; - for (var screenRow = pY + 1; screenRow < endScreenRow; ++screenRow) - { - console.gotoxy(pX, screenRow); - console.print(borderChars.vert); - console.gotoxy(rightCol, screenRow); - console.print(borderChars.vert); - } - // Bottom border - console.gotoxy(pX, endScreenRow); - console.print(borderChars.LL); - innerWidth = pWidth - 2; - // Include the bottom border text in the top border, if there is any specified - if (typeof(pBottomBorderText) === "string" && pBottomBorderText.length > 0) - { - var textLen = strip_ctrl(pBottomBorderText).length; - if (textLen > pWidth - 4) - textLen = pWidth - 4; - innerWidth -= textLen; - innerWidth -= 2; // ?? Correctional - // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js - console.print(borderChars.preText + "\x01n" + substrWithAttrCodes(pBottomBorderText, 0, textLen) + - "\x01n" + pColor + borderChars.postText); - if (innerWidth > 0) - console.print(pColor); - } - for (var i = 0; i < innerWidth; ++i) - console.print(borderChars.horiz); - console.print(borderChars.LR); -} - -// Draws a horizontal separator line on the screen, in high green -// -// Parameters: -// pX: The X coordinate to start at -// pY: The Y coordinate to start at -// pWidth: The width of the line to draw -function drawSeparatorLine(pX, pY, pWidth) -{ - if (typeof(pX) !== "number" || typeof(pY) !== "number" || typeof(pWidth) !== "number") - return; - if (pX < 1 || pX > console.screen_columns) - return; - if (pY < 1 || pY > console.screen_rows) - return; - if (pWidth < 1) - return; - - var width = pWidth; - var maxWidth = console.screen_columns - pX + 1; - if (width > maxWidth) - width = maxWidth; - - console.gotoxy(pX, pY); - console.print("\x01n\x01g\x01h"); - for (var i = 0; i < width; ++i) - console.print(HORIZONTAL_SINGLE); - console.print("\x01n"); -} - -// Confirms with the user to perform an action with a file or set of files -// -// Parameters: -// pFilenames: An array of filenames (as strings), or a string containing a filename -// pActionName: String - The name of the action to confirm -// pDefaultYes: Boolean - True if the default is to be yes, or false if no -// -// Return value: Boolean - True if the user confirmed, or false if not -function confirmFileActionWithUser(pFilenames, pActionName, pDefaultYes) -{ - if (typeof(pFilenames) !== "object" && typeof(pFilenames) !== "string") - return false; - if (typeof(pActionName) !== "string") - return false; - - var actionConfirmed = false; - - var numFilenames = 1; - if (typeof(pFilenames) === "object") - numFilenames = pFilenames.length; - if (numFilenames < 1) - return false; - // If there is only 1 filename, then prompt the user near the bottom of the screen - else if (numFilenames == 1) - { - var filename = (typeof(pFilenames) === "string" ? pFilenames : pFilenames[0]); - drawSeparatorLine(1, console.screen_rows-2, console.screen_columns-1); - console.gotoxy(1, console.screen_rows-1); - console.cleartoeol("\x01n"); - console.gotoxy(1, console.screen_rows-1); - var shortFilename = shortenFilename(filename, Math.floor(console.screen_columns/2), false); - if (pDefaultYes) - actionConfirmed = console.yesno(pActionName + " " + shortFilename); - else - actionConfirmed = !console.noyes(pActionName + " " + shortFilename); - // Refresh the main screen content, to erase the confirmation prompt - refreshScreenMainContent(1, console.screen_rows-2, console.screen_columns, 2, true); - } - else - { - // Construct & draw a frame with the file list & display the frame to confirm with the - // user to delete the files - var frameUpperLeftX = gFileListMenu.pos.x + 2; - var frameUpperLeftY = gFileListMenu.pos.y + 2; - //var frameWidth = gFileListMenu.size.width - 4; - var frameWidth = console.screen_columns - 4; - var frameHeight = 10; - var frameTitle = pActionName + " files? (Y/N)"; - var additionalQuitKeys = "yYnN"; - var frameInnerWidth = frameWidth - 2; // Without borders; for filename lengths - var fileListStr = "\x01n\x01w"; - for (var i = 0; i < pFilenames.length; ++i) - fileListStr += shortenFilename(pFilenames[i], frameInnerWidth, false) + "\r\n"; - var lastUserInput = displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, - frameHeight, gColors.confirmFileActionWindowBorder, - frameTitle, gColors.confirmFileActionWindowWindowTitle, - fileListStr, additionalQuitKeys); - actionConfirmed = (lastUserInput.toUpperCase() == "Y"); - // Refresh the main screen content, to erase the confirmation window - refreshScreenMainContent(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight, true); - } - - return actionConfirmed; -} - - -// Reads the configuration file and sets the settings accordingly -function readConfigFile() -{ - this.cfgFileSuccessfullyRead = false; - - var themeFilename = ""; // In case a theme filename is specified - - // Determine the script's startup directory. - // This code is a trick that was created by Deuce, suggested by Rob Swindell - // as a way to detect which directory the script was executed in. I've - // shortened the code a little. - var startupPath = '.'; - try { throw dig.dist(dist); } catch(e) { startupPath = e.fileName; } - startupPath = backslash(startupPath.replace(/[\/\\][^\/\\]*$/,'')); - - // Open the main configuration file. First look for it in the sbbs/mods - // directory, then sbbs/ctrl, then in the same directory as this script. - var cfgFilename = "ddfilelister.cfg"; - var cfgFilenameFullPath = file_cfgname(system.mods_dir, cfgFilename); - if (!file_exists(cfgFilenameFullPath)) - cfgFilenameFullPath = file_cfgname(system.ctrl_dir, cfgFilename); - if (!file_exists(cfgFilenameFullPath)) - cfgFilenameFullPath = file_cfgname(startupPath, cfgFilename); - var cfgFile = new File(cfgFilenameFullPath); - if (cfgFile.open("r")) - { - this.cfgFileSuccessfullyRead = true; - - var fileLine = null; // A line read from the file - var equalsPos = 0; // Position of a = in the line - var commentPos = 0; // Position of the start of a comment - var setting = null; // A setting name (string) - var settingUpper = null; // Upper-case setting name - var value = null; // To store a value for a setting (string) - while (!cfgFile.eof) - { - // Read the next line from the config file. - fileLine = cfgFile.readln(2048); - - // fileLine should be a string, but I've seen some cases - // where it isn't, so check its type. - if (typeof(fileLine) != "string") - continue; - - // If the line starts with with a semicolon (the comment - // character) or is blank, then skip it. - if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) - continue; - - // If the line has a semicolon anywhere in it, then remove - // everything from the semicolon onward. - commentPos = fileLine.indexOf(";"); - if (commentPos > -1) - fileLine = fileLine.substr(0, commentPos); - - // Look for an equals sign, and if found, separate the line - // into the setting name (before the =) and the value (after the - // equals sign). - equalsPos = fileLine.indexOf("="); - if (equalsPos > 0) - { - // Read the setting & value, and trim leading & trailing spaces. - setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); - settingUpper = setting.toUpperCase(); - value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true); - var valueUpper = value.toUpperCase(); - - // Set the appropriate valueUpper in the settings object. - if (settingUpper == "SORTORDER") - { - // FileBase.SORT properties - // Name Type Description - // NATURAL number Natural sort order (same as DATE_A) - // NAME_AI number Filename ascending, case insensitive sort order - // NAME_DI number Filename descending, case insensitive sort order - // NAME_AS number Filename ascending, case sensitive sort order - // NAME_DS number Filename descending, case sensitive sort order - // DATE_A number Import date/time ascending sort order - // DATE_D number Import date/time descending sort order - if (valueUpper == "NATURAL") - gFileSortOrder = FileBase.SORT.NATURAL; - else if (valueUpper == "NAME_AI") - gFileSortOrder = FileBase.SORT.NAME_AI; - else if (valueUpper == "NAME_DI") - gFileSortOrder = FileBase.SORT.NAME_DI; - else if (valueUpper == "NAME_AS") - gFileSortOrder = FileBase.SORT.NAME_AS; - else if (valueUpper == "NAME_DS") - gFileSortOrder = FileBase.SORT.NAME_DS; - else if (valueUpper == "DATE_A") - gFileSortOrder = FileBase.SORT.DATE_A; - else if (valueUpper == "DATE_D") - gFileSortOrder = FileBase.SORT.DATE_D; - else if (valueUpper == "ULTIME") - gFileSortOrder = SORT_FL_ULTIME; - else if (valueUpper == "DLTIME") - gFileSortOrder = SORT_FL_DLTIME; - else // Default - gFileSortOrder = FileBase.SORT.NATURAL; - } - else if (settingUpper == "PAUSEAFTERVIEWINGFILE") - gPauseAfterViewingFile = (value.toUpperCase() == "TRUE"); - else if (settingUpper == "THEMEFILENAME") - { - // First look for the theme config file in the sbbs/mods - // directory, then sbbs/ctrl, then the same directory as - // this script. - themeFilename = system.mods_dir + value; - if (!file_exists(themeFilename)) - themeFilename = system.ctrl_dir + value; - if (!file_exists(themeFilename)) - themeFilename = startupPath + value; - } - } - } - - cfgFile.close(); - } - else - { - // Was unable to read the configuration file. Output a warning to the user - // that defaults will be used and to notify the sysop. - console.print("\x01n"); - console.crlf(); - console.print("\x01w\x01hUnable to open the configuration file: \x01y" + cfgFilename); - console.crlf(); - console.print("\x01wDefault settings will be used. Please notify the sysop."); - mswait(2000); - } - - // If a theme filename was specified, then read the colors & strings - // from it. - if (themeFilename.length > 0) - { - var themeFile = new File(themeFilename); - if (themeFile.open("r")) - { - var fileLine = null; // A line read from the file - var equalsPos = 0; // Position of a = in the line - var commentPos = 0; // Position of the start of a comment - var setting = null; // A setting name (string) - var value = null; // To store a value for a setting (string) - while (!themeFile.eof) - { - // Read the next line from the config file. - fileLine = themeFile.readln(2048); - - // fileLine should be a string, but I've seen some cases - // where it isn't, so check its type. - if (typeof(fileLine) != "string") - continue; - - // If the line starts with with a semicolon (the comment - // character) or is blank, then skip it. - if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) - continue; - - // If the line has a semicolon anywhere in it, then remove - // everything from the semicolon onward. - commentPos = fileLine.indexOf(";"); - if (commentPos > -1) - fileLine = fileLine.substr(0, commentPos); - - // Look for an equals sign, and if found, separate the line - // into the setting name (before the =) and the value (after the - // equals sign). - equalsPos = fileLine.indexOf("="); - if (equalsPos > 0) - { - // Read the setting (without leading/trailing spaces) & value - setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); - value = fileLine.substr(equalsPos+1); - - if (gColors.hasOwnProperty(setting)) - { - // Trim leading & trailing spaces from the value when - // setting a color. Also, replace any instances of "\x01" or "\1" - // with the Synchronet attribute control character. - gColors[setting] = trimSpaces(value, true, false, true).replace(/\\[xX]01/g, "\x01").replace(/\\1/g, "\x01"); - } - } - } - - themeFile.close(); - } - else - { - // Was unable to read the theme file. Output a warning to the user - // that defaults will be used and to notify the sysop. - this.cfgFileSuccessfullyRead = false; - console.print("\x01n"); - console.crlf(); - console.print("\x01w\x01hUnable to open the theme file: \x01y" + themeFilename); - console.crlf(); - console.print("\x01wDefault colors will be used. Please notify the sysop."); - mswait(2000); - } - } -} - -// Removes multiple, leading, and/or trailing spaces -// The search & replace regular expressions used in this -// function came from the following URL: -// http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces -// -// Parameters: -// pString: The string to trim -// pLeading: Whether or not to trim leading spaces (optional, defaults to true) -// pMultiple: Whether or not to trim multiple spaces (optional, defaults to true) -// pTrailing: Whether or not to trim trailing spaces (optional, defaults to true) -// -// Return value: The string with whitespace trimmed -function trimSpaces(pString, pLeading, pMultiple, pTrailing) -{ - if (typeof(pString) !== "string") - return ""; - - var leading = (typeof(pLeading) === "boolean" ? pLeading : true); - var multiple = (typeof(pMultiple) === "boolean" ? pMultiple : true); - var trailing = (typeof(pTrailing) === "boolean" ? pTrailing : true); - - // To remove both leading & trailing spaces: - //pString = pString.replace(/(^\s*)|(\s*$)/gi,""); - - var string = pString; - if (leading) - string = string.replace(/(^\s*)/gi,""); - if (multiple) - string = string.replace(/[ ]{2,}/gi," "); - if (trailing) - string = string.replace(/(\s*$)/gi,""); - - return string; -} - -// Given a filename, this returns a filename shortened to a maximum length, preserving the -// filename extension. -// -// Parameters: -// pFilename: The filename to shorten -// pMaxLen: The maximum length of the shortened filename -// pFillWidth: Boolean - Whether or not to fill the width specified by pMaxLen -// -// Return value: A string with the shortened filename -function shortenFilename(pFilename, pMaxLen, pFillWidth) -{ - if (typeof(pFilename) !== "string") - return ""; - if (typeof(pMaxLen) !== "number" || pMaxLen < 1) - return ""; - - var shortenedFilename = ""; // Will contain the shortened filename - // Get the filename extension. And the way we shorten the filename - // will depend on whether the filename actually has an extension or not. - var filenameExt = file_getext(pFilename); - if (typeof(filenameExt) === "string") - { - var filenameWithoutExt = file_getname(pFilename); - var extIdx = filenameWithoutExt.indexOf(filenameExt); - if (extIdx >= 0) - filenameWithoutExt = filenameWithoutExt.substr(0, extIdx); - - var maxWithoutExtLen = pMaxLen - filenameExt.length; - if (filenameWithoutExt.length > maxWithoutExtLen) - filenameWithoutExt = filenameWithoutExt.substr(0, maxWithoutExtLen); - - var fillWidth = (typeof(pFillWidth) === "boolean" ? pFillWidth : false); - if (fillWidth) - shortenedFilename = format("%-" + maxWithoutExtLen + "s%s", filenameWithoutExt, filenameExt); - else - shortenedFilename = filenameWithoutExt + filenameExt; - } - else - shortenedFilename = pFilename.substr(0, pMaxLen); - return shortenedFilename; -} - - -// Parses command-line arguments, where each argument in the given array is in -// the format -arg=val. If the value is the string "true" or "false", then the -// value will be a boolean. Otherwise, the value will be a string. -// -// Parameters: -// argv: An array of strings containing values in the format -arg=val -// -// Return value: Boolean: Whether or not this script was run as a loadable -// module (depending on the arguments used). -function parseArgs(argv) -{ - // Default program options - gScriptMode = MODE_LIST_DIR; - - // Sanity checking for argv - Make sure it's an array - if ((typeof(argv) != "object") || (typeof(argv.length) != "number")) - return false; - - // This script accepts arguments in -arg=val format; See if there are any of those. - // If so, process arguments with that assumption; otherwise, we'll check for args - // passed when Synchronet runs this as a loadable module. - var scriptRanAsLoadableModule = false; - var anySettingEqualsVal = false; - for (var i = 0; i < argv.length && !anySettingEqualsVal; ++i) - anySettingEqualsVal = (typeof(argv[i]) === "string" && argv[i].length > 0 && argv[i].charAt(0) == "-" && argv[i].indexOf("=") > 1); - if (anySettingEqualsVal) - { - // Go through argv looking for strings in the format -arg=val and parse them - // into objects in the argVals array. - var equalsIdx = 0; - var argName = ""; - var argVal = ""; - var argValUpper = ""; // For case-insensitive matching - for (var i = 0; i < argv.length; ++i) - { - // We're looking for strings that start with "-", except strings that are - // only "-". - if (typeof(argv[i]) !== "string" || argv[i].length == 0 || argv[i].charAt(0) != "-" || argv[i] == "-") - continue; - - // Look for an = and if found, split the string on the = - equalsIdx = argv[i].indexOf("="); - // If a = is found, then split on it and add the argument name & value - // to the array. Otherwise (if the = is not found), then treat the - // argument as a boolean and set it to true (to enable an option). - if (equalsIdx > -1) - { - argName = argv[i].substring(1, equalsIdx).toUpperCase(); - argVal = argv[i].substr(equalsIdx+1); - argValUpper = argVal.toUpperCase(); - if (argName === "MODE") - { - if (argValUpper === "SEARCH_FILENAME") - gScriptMode = MODE_SEARCH_FILENAME; - else if (argValUpper === "SEARCH_DESCRIPTION") - gScriptMode = MODE_SEARCH_DESCRIPTION; - else if (argValUpper === "NEW_FILE_SEARCH") - gScriptMode = MODE_NEW_FILE_SEARCH; - else if (argValUpper === "LIST_CURDIR") - gScriptMode = MODE_LIST_DIR; - } - } - // Nothing to do if an = was not found - } - } - else - { - // Check for arguments as if this was run by Synchronet as a loadable module - // (for either Scan Dirs or List Files) - - /* - // bbs.list_files() & bbs.scan_dirs() - //******************************************** - var FL_NONE =0; // No special behavior - var FL_ULTIME =(1<<0); // List files by upload time - var FL_DLTIME =(1<<1); // List files by download time - var FL_NO_HDR =(1<<2); // Don't list directory header - var FL_FINDDESC =(1<<3); // Find text in description - var FL_EXFIND =(1<<4); // Find text in description - extended info - var FL_VIEW =(1<<5); // View ZIP/ARC/GIF etc. info - */ - - // There must be either 2 or 3 arguments - if (argv.length < 2) - return false; - // The 2nd argument is the mode/behavior bits in either case - var FLBehavior = parseInt(argv[1]); - if (isNaN(FLBehavior)) - return false; - else - gListBehavior = FLBehavior; - scriptRanAsLoadableModule = true; - - // Default gScriptmode to MODE_LIST_DIR; for FLBehavior as FL_NONE, no special behavior - gScriptMode = MODE_LIST_DIR; - - // 2 args - Scanning/searching - if (argv.length == 2) - { - // - 0: Bool (scanning all directories): 0/1 - // - 1: FL_ mode value - gScanAllDirs = (argv[0] == "1"); - if ((FLBehavior & FL_ULTIME) == FL_ULTIME) - gScriptMode = MODE_NEW_FILE_SEARCH; - else if ((FLBehavior & FL_FINDDESC) == FL_FINDDESC || (FLBehavior & FL_EXFIND) == FL_EXFIND) - gScriptMode = MODE_SEARCH_DESCRIPTION; - if ((FLBehavior & FL_VIEW) == FL_VIEW) - { - // View ZIP/ARC/GIF etc. info - // TODO: Not sure what to do with this - } - } - // 3 args - Listing - else if (argv.length >= 3) //==3 - { - // - 0: Directory internal code - // - 1: FL_ mode value - // - 2: Filespec (i.e., *, *.zip, etc.) - if (!file_area.dir.hasOwnProperty(argv[0])) - return false; - gDirCode = argv[0]; - gFilespec = argv[2]; - if ((FLBehavior & FL_VIEW) == FL_VIEW) - { - // View ZIP/ARC/GIF etc. info - // TODO: Not sure what to do with this - } - } - - // Options that apply to both searching and listing - if ((FLBehavior & FL_ULTIME) == FL_ULTIME) - gFileSortOrder = SORT_FL_ULTIME; - else if ((FLBehavior & FL_DLTIME) == FL_DLTIME) - gFileSortOrder = SORT_FL_DLTIME; - - } - - return scriptRanAsLoadableModule; -} - -// Populates the file list (gFileList). -// -// Parameters: -// pSearchMode: The search mode -// -// Return value: An obmect with the following properties: -// exitNow: Boolean: Whether or not this script should exit after calling this function -// exitCode: The exit code to use if needing to exit after calling this function -function populateFileList(pSearchMode) -{ - var retObj = { - exitNow: false, - exitCode: 0 - }; - - var dirErrors = []; - var allSameDir = true; - - // Do the things for list or search, depending on the specified mode - if (pSearchMode == MODE_LIST_DIR) // This is the default - { - var filebase = new FileBase(gDirCode); - if (filebase.open()) - { - // If there are no files in the filebase, then say so and exit now. - if (filebase.files == 0) - { - filebase.close(); - var libIdx = file_area.dir[gDirCode].lib_index; - console.crlf(); - console.print("\x01n\x01cThere are no files in \x01h" + file_area.lib_list[libIdx].description + "\x01n\x01c - \x01h" + - file_area.dir[gDirCode].description + "\x01n"); - console.crlf(); - console.pause(); - retObj.exitNow = true; - retObj.exitCode = 0; - return retObj; - } - - // Get a list of file data - var fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); - if (gFileSortOrder == SORT_FL_ULTIME || gFileSortOrder == SORT_FL_DLTIME) - { - gFileList = filebase.get_list(gFilespec, fileDetail, 0, true, FileBase.SORT.NATURAL); - if (gFileSortOrder == SORT_FL_ULTIME) - gFileList.sort(fileInfoSortULTime); - else if (gFileSortOrder == SORT_FL_DLTIME) - gFileList.sort(fileInfoSortDLTime); - } - else - gFileList = filebase.get_list(gFilespec, fileDetail, 0, true, gFileSortOrder); - filebase.close(); - // Add a dirCode property to the file metadata objects (for consistency, - // as file search results may contain files from multiple directories). - // Also, if the metadata objects have an extdesc, remove any trailing CRLF - // from the end. - for (var i = 0; i < gFileList.length; ++i) - { - gFileList[i].dirCode = gDirCode; - if (gFileList[i].hasOwnProperty("extdesc") && /\r\n$/.test(gFileList[i].extdesc)) - { - gFileList[i].extdesc = gFileList[i].extdesc.substr(0, gFileList[i].extdesc.length-2); - // Fix line endings if necessary - gFileList[i].extdesc = lfexpand(gFileList[i].extdesc); - } - } - } - else - { - console.crlf(); - console.print("\x01n\x01h\x01yUnable to open \x01w" + file_area.dir[gDirCode].description + "\x01n"); - console.crlf(); - console.pause(); - retObj.exitNow = true; - retObj.exitCode = 1; - return retObj; - } - } - else if (pSearchMode == MODE_SEARCH_FILENAME) - { - var lastDirCode = ""; - - // If not searching all already, prompt the user for directory, library, or all - var validInputOptions = "DLA"; - var userInputDLA = ""; - if (gScanAllDirs) - userInputDLA = "A"; - else - { - console.attributes = "N"; - console.crlf(); - console.mnemonics(bbs.text(DirLibOrAll)); - userInputDLA = console.getkeys(validInputOptions, -1, K_UPPER); - } - var userFilespec = ""; - if (userInputDLA.length > 0 && validInputOptions.indexOf(userInputDLA) > -1) - { - // Prompt the user for a filespec to search for - console.mnemonics(bbs.text(FileSpecStarDotStar)); - userFilespec = console.getstr(); - if (userFilespec.length == 0) - userFilespec = "*"; // Should this be *.*? - console.print("Searching...."); - } - var searchRetObj = searchDirGroupOrAll(userInputDLA, function(pDirCode) { - return searchDirWithFilespec(pDirCode, userFilespec); - }); - allSameDir = searchRetObj.allSameDir; - for (var i = 0; i < searchRetObj.errors.length; ++i) - dirErrors.push(searchRetObj.errors[i]); - } - else if (pSearchMode == MODE_SEARCH_DESCRIPTION) - { - var lastDirCode = ""; - - // If not saerching all already, prompt the user for directory, library, or all - var validInputOptions = "DLA"; - var userInputDLA = ""; - if (gScanAllDirs) - userInputDLA = "A"; - else - { - console.print("\x01n"); - console.crlf(); - //console.print("\r\n\x01c\x01hFind Text in File Descriptions (no wildcards)\x01n\r\n"); - console.mnemonics(bbs.text(DirLibOrAll)); - console.print("\x01n"); - userInputDLA = console.getkeys(validInputOptions, -1, K_UPPER); - } - var searchDescription = ""; - if (userInputDLA.length > 0 && validInputOptions.indexOf(userInputDLA) > -1) - { - // Prompt the user for a description to search for - console.mnemonics(bbs.text(SearchStringPrompt)); - searchDescription = console.getstr(40, K_LINE|K_UPPER); - if (searchDescription.length > 0) - console.print("Searching...."); - else - { - retObj.exitNow = true; - retObj.exitCode = 0; - return retObj; - } - } - var searchRetObj = searchDirGroupOrAll(userInputDLA, function(pDirCode) { - return searchDirWithDescUpper(pDirCode, searchDescription); - }); - allSameDir = searchRetObj.allSameDir; - for (var i = 0; i < searchRetObj.errors.length; ++i) - dirErrors.push(searchRetObj.errors[i]); - } - else if (pSearchMode == MODE_NEW_FILE_SEARCH) - { - // New file search - var lastDirCode = ""; - - // 2022-02-011 - Digital Man said: - /* - Upon logon to the terminal server, bbs.new_file_time and bbs.last_new_file_time - are set to the current user.new_file_time value. - - A new file scan displays files uploaded (added/imported) since the current - bbs.new_file_time value. The bbs.new_file_time value can be manipulated by the - user, e.g. they want to review files that have been uploaded over the past - month or whatever. - - When the user executes a new file scan, bbs.last_new_file_time is updated to - the current time (this happens automatically when using the built-in file - listing logic). The bbs.last_new_file_time isn't actually used for anything - until the user logs-off and its value is then copied to the user.new_file_time - to be used for the next logon (this copy/sync of the last_new_file_time -> - user.new_file_time is built-in to the BBS's logout logci and nothing a script - would need to do). - - This scheme insures that a user will never "miss" the display of new files on - the BBS and that they would not normally see the same/repeat "new" files - between successive sessions. - */ - - // If not searching all dirs already, prompt the user for directory, library, or all - var userInputDLA = ""; - if (gScanAllDirs) - userInputDLA = "A"; - else - { - console.print("\x01n"); - console.crlf(); - console.mnemonics(bbs.text(DirLibOrAll)); - var validInputOptions = "DLA"; - userInputDLA = console.getkeys(validInputOptions, -1, K_UPPER); - console.print("\x01n"); - console.crlf(); - } - if (userInputDLA == "D" || userInputDLA == "L" || userInputDLA == "A") - { - console.print("\x01n\x01cSearching for files uploaded after \x01h" + system.timestr(bbs.new_file_time) + "\x01n"); - console.crlf(); - } - var searchRetObj = searchDirGroupOrAll(userInputDLA, function(pDirCode) { - return searchDirNewFiles(pDirCode, bbs.new_file_time); - }); - // Now bbs.last_new_file_time needs to be updated with the current time - bbs.last_new_file_time = time(); - // user.new_file_time should be updated with the value of bbs.last_new_file_time - // when the user logs off. - allSameDir = searchRetObj.allSameDir; - for (var i = 0; i < searchRetObj.errors.length; ++i) - dirErrors.push(searchRetObj.errors[i]); - } - else - { - retObj.exitNow = true; - retObj.exitCode = 1; - return retObj; - } - - if (pSearchMode != MODE_LIST_DIR) - gFileList.allSameDir = allSameDir; - - if (dirErrors.length > 0) - { - console.print("\x01n\x01y\x01h"); - for (var i = 0; i < dirErrors.length; ++i) - { - console.print(dirErrors[i]); - console.crlf(); - } - console.print("\x01n"); - console.pause(); - retObj.exitNow = true; - retObj.exitCode = 1; - return retObj; - } - - // Figure out the longest filename in the list. - var longestFilenameLen = 0; - for (var i = 0; i < gFileList.length; ++i) - { - if (gFileList[i].name.length > longestFilenameLen) - longestFilenameLen = gFileList[i].name.length; - } - var displayFilenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart + 1; - // If the user has extended descriptions enabled, then allow 47 characters for the - // description and adjust the filename length accordingly - gListIdxes.descriptionEnd = console.screen_columns - 1; // Leave 1 character remaining on the screen - if (extendedDescEnabled()) - { - gListIdxes.descriptionStart = gListIdxes.descriptionEnd - 47 + 1; - gListIdxes.fileSizeEnd = gListIdxes.descriptionStart; - gListIdxes.fileSizeStart = gListIdxes.fileSizeEnd - 7; - gListIdxes.filenameEnd = gListIdxes.fileSizeStart; - } - // If not displaying extended descriptions, then if the longest filename - // is longer than the current display filename length and the user's - // terminal is at least 100 columns wide, then increase the filename length - // for the list by 20; - else if (longestFilenameLen > displayFilenameLen && console.screen_columns >= 100) - { - gListIdxes.filenameEnd += 20; - gListIdxes.fileSizeStart = gListIdxes.filenameEnd; - gListIdxes.fileSizeEnd = gListIdxes.fileSizeStart + 7; - gListIdxes.descriptionStart = gListIdxes.fileSizeEnd; - } - - return retObj; -} - -// Searches files in a directory by filespec. -// -// Parameters: -// pDirCode: The internal code of a directory to search -// pFilespec: A filespec describing files to search for -// -// Return value: An object with the following properties: -// foundFiles: Boolean - Whether or not any files were found -// dirErrors: An array of strings that will be populated with any errors that occur -function searchDirWithFilespec(pDirCode, pFilespec) -{ - var retObj = { - foundFiles: false, - dirErrors: [] - }; - if (typeof(pDirCode) !== "string" || pDirCode.length == 0 || typeof(pFilespec) !== "string") - return retObj; - - var filebase = new FileBase(pDirCode); - if (filebase.open()) - { - var fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); - var fileList; - if (gFileSortOrder == SORT_FL_ULTIME || gFileSortOrder == SORT_FL_DLTIME) - { - fileList = filebase.get_list(pFilespec, fileDetail, 0, true, FileBase.SORT.NATURAL); - if (gFileSortOrder == SORT_FL_ULTIME) - fileList.sort(fileInfoSortULTime); - else if (gFileSortOrder == SORT_FL_DLTIME) - fileList.sort(fileInfoSortDLTime); - } - else - fileList = filebase.get_list(pFilespec, fileDetail, 0, true, gFileSortOrder); - retObj.foundFiles = (fileList.length > 0); - filebase.close(); - for (var i = 0; i < fileList.length; ++i) - { - // Fix line endings in extdesc if necessary - if (fileList[i].hasOwnProperty("extdesc") && /\r\n$/.test(fileList[i].extdesc)) - fileList[i].extdesc = lfexpand(fileList[i].extdesc); - fileList[i].dirCode = pDirCode; - gFileList.push(fileList[i]); - } - } - else - { - var libAndDirDesc = file_area.lib_list[libIdx].description + " - " - + file_area.lib_list[libIdx].dir_list[dirIdx].description + " (" - + file_area.lib_list[libIdx].dir_list[dirIdx].code + ")"; - retObj.dirErrors.push("Failed to open " + libAndDirDesc); - } - return retObj; -} - -// Searches files in a directory by description. -// -// Parameters: -// pDirCode: The internal code of a directory to search -// pDescUpper: The description to search for. This is assumed to be uppercase. -// -// Return value: An object with the following properties: -// foundFiles: Boolean - Whether or not any files were found -// dirErrors: An array of strings that will be populated with any errors that occur -function searchDirWithDescUpper(pDirCode, pDescUpper) -{ - var retObj = { - foundFiles: false, - dirErrors: [] - }; - if (typeof(pDirCode) !== "string" || pDirCode.length == 0 || typeof(pDescUpper) !== "string" || pDescUpper.length == 0) - return retObj; - - var filebase = new FileBase(pDirCode); - if (filebase.open()) - { - //var fileList = filebase.get_list(gFilespec, FileBase.DETAIL.EXTENDED, 0, true, gFileSortOrder); - var fileList; - if (gFileSortOrder == SORT_FL_ULTIME || gFileSortOrder == SORT_FL_DLTIME) - { - fileList = filebase.get_list(gFilespec, FileBase.DETAIL.EXTENDED, 0, true, FileBase.SORT.NATURAL); - if (gFileSortOrder == SORT_FL_ULTIME) - fileList.sort(fileInfoSortULTime); - else if (gFileSortOrder == SORT_FL_DLTIME) - fileList.sort(fileInfoSortDLTime); - } - else - fileList = filebase.get_list(gFilespec, FileBase.DETAIL.EXTENDED, 0, true, gFileSortOrder); - filebase.close(); - for (var i = 0; i < fileList.length; ++i) - { - // Search both 'desc' and 'extdesc', if available in the file metadata object - var fileIsMatch = false; - if (fileList[i].hasOwnProperty("desc")) - { - var fileDesc = strip_ctrl(fileList[i].desc).toUpperCase(); - fileIsMatch = (fileDesc.indexOf(pDescUpper) > -1); - } - if (!fileIsMatch && fileList[i].hasOwnProperty("extdesc")) - { - // Fix line endings if necessary - fileList[i].extdesc = lfexpand(fileList[i].extdesc); - var descLines = fileList[i].extdesc.split("\r\n"); - var fileDesc = strip_ctrl(descLines.join(" ")).toUpperCase(); - fileIsMatch = (fileDesc.indexOf(pDescUpper) > -1); - } - if (fileIsMatch) - { - retObj.foundFiles = true; - fileList[i].dirCode = pDirCode; - gFileList.push(fileList[i]); - } - } - } - else - { - var libAndDirDesc = file_area.lib_list[libIdx].description + " - " - + file_area.lib_list[libIdx].dir_list[dirIdx].description + " (" - + file_area.lib_list[libIdx].dir_list[dirIdx].code + ")"; - retObj.dirErrors.push("Failed to open " + libAndDirDesc); - } - return retObj; -} - -// Searches files in a directory that were added later than a given time. -// -// Parameters: -// pDirCode: The internal code of a directory to search -// pSinceTime: The time after which to look for newer files -// -// Return value: An object with the following properties: -// foundFiles: Boolean - Whether or not any files were found -// dirErrors: An array of strings that will be populated with any errors that occur -function searchDirNewFiles(pDirCode, pSinceTime) -{ - var retObj = { - foundFiles: false, - dirErrors: [] - }; - if (typeof(pDirCode) !== "string" || pDirCode.length == 0 || typeof(pSinceTime) !== "number") - return retObj; - - var filebase = new FileBase(pDirCode); - if (filebase.open()) - { - var fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); - var fileList; - if (gFileSortOrder == SORT_FL_ULTIME || gFileSortOrder == SORT_FL_DLTIME) - { - fileList = filebase.get_list(gFilespec, fileDetail, 0, true, FileBase.SORT.NATURAL); - if (gFileSortOrder == SORT_FL_ULTIME) - fileList.sort(fileInfoSortULTime); - else if (gFileSortOrder == SORT_FL_DLTIME) - fileList.sort(fileInfoSortDLTime); - } - else - fileList = filebase.get_list(gFilespec, fileDetail, 0, true, gFileSortOrder); - filebase.close(); - for (var i = 0; i < fileList.length; ++i) - { - if (fileList[i].added >= pSinceTime) - { - retObj.foundFiles = true; - // Fix line endings in extdesc if necessary - if (fileList[i].hasOwnProperty("extdesc")) - fileList[i].extdesc = lfexpand(fileList[i].extdesc); - fileList[i].dirCode = pDirCode; - gFileList.push(fileList[i]); - } - } - } - else - { - var libAndDirDesc = file_area.lib_list[libIdx].description + " - " - + file_area.lib_list[libIdx].dir_list[dirIdx].description + " (" - + file_area.lib_list[libIdx].dir_list[dirIdx].code + ")"; - retObj.dirErrors.push("Failed to open " + libAndDirDesc); - } - return retObj; -} - -// Searches either the user's current directory, library, or all dirs in all -// libraries, using a search function that populages gFileList. -// -// Parameters: -// pSearchOption: A string that specifies "D" for directory, "L" for library, or "A" for all -// pDirSearchFn: A search function that searches a file directory for criteria and populates gFileList. -// This function must return an object with the following properties: -// foundFiles: Boolean - Whether any files were found -// dirErrors: An array containing strings for any errors found -// -// Return value: An object with the following properties: -// allSameDir: Boolean - Whethre or not all files found are in the same directory -// errors: An array containing strings for any errors found -function searchDirGroupOrAll(pSearchOption, pDirSearchFn) -{ - var retObj = { - allSameDir: true, - errors: [] - }; - - if (typeof(pSearchOption) !== "string") - { - retObj.errors.push("Invalid search option"); - return retObj; - } - if (typeof(pDirSearchFn) !== "function") - { - retObj.errors.push("No search function"); - return retObj; - } - - var searchOptionUpper = pSearchOption.toUpperCase(); - if (searchOptionUpper == "D") - { - // Current directory - var searchRetObj = pDirSearchFn(gDirCode); - retObj.allSameDir = true; - for (var i = 0; i < searchRetObj.dirErrors.length; ++i) - retObj.errors.push(searchRetObj.dirErrors[i]); - } - else if (searchOptionUpper == "L") - { - // Current file library - var libIdx = file_area.dir[gDirCode].lib_index; - for (var dirIdx = 0; dirIdx < file_area.lib_list[libIdx].dir_list.length; ++dirIdx) - { - if (file_area.lib_list[libIdx].dir_list[dirIdx].can_access) // And can_download? - { - if (gSearchVerbose) - { - console.print(" " + file_area.lib_list[libIdx].dir_list[dirIdx].description + ".."); - console.crlf(); - } - var lastDirCode = (gFileList.length > 0 ? gFileList[gFileList.length-1].dirCode : ""); - var dirCode = file_area.lib_list[libIdx].dir_list[dirIdx].code; - var searchRetObj = pDirSearchFn(dirCode); - if (retObj.allSameDir && searchRetObj.foundFiles && lastDirCode.length > 0) - retObj.allSameDir = (dirCode == lastDirCode); - for (var i = 0; i < searchRetObj.dirErrors.length; ++i) - retObj.errors.push(searchRetObj.dirErrors[i]); - } - } - } - else if (searchOptionUpper == "A") - { - // All file libraries & directories - for (var libIdx = 0; libIdx < file_area.lib_list.length; ++libIdx) - { - if (gSearchVerbose) - { - console.print("Searching " + file_area.lib_list[libIdx].description + ".."); - console.crlf(); - } - for (var dirIdx = 0; dirIdx < file_area.lib_list[libIdx].dir_list.length; ++dirIdx) - { - if (file_area.lib_list[libIdx].dir_list[dirIdx].can_access) // And can_download? - { - if (gSearchVerbose) - { - console.print(" " + file_area.lib_list[libIdx].dir_list[dirIdx].description + ".."); - console.crlf(); - } - var lastDirCode = (gFileList.length > 0 ? gFileList[gFileList.length-1].dirCode : ""); - var dirCode = file_area.lib_list[libIdx].dir_list[dirIdx].code; - var searchRetObj = pDirSearchFn(dirCode); - if (retObj.allSameDir && searchRetObj.foundFiles && lastDirCode.length > 0) - retObj.allSameDir = (dirCode == lastDirCode); - for (var i = 0; i < searchRetObj.dirErrors.length; ++i) - retObj.errors.push(searchRetObj.dirErrors[i]); - } - } - } - } - else - { - //retObj.errors.push("Invalid search option" + (pSearchOption.length > 0 ? ": " + pSearchOption : "")); - retObj.errors.push("Aborted"); - } - - return retObj; -} - -// Returns whether the user has their extended file description setting enabled -// and if it can be supported in the user's terminal mode. Extended descriptions -// will be displayed in the main screen if the user has that option enabled and -// the user's terminal is at least 80 columns wide. -function extendedDescEnabled() -{ - var userExtDescEnabled = ((user.settings & USER_EXTDESC) == USER_EXTDESC); - return userExtDescEnabled && console.screen_columns >= 80; -} - -// Displays a file's extended description on the main screen, next to the -// file list menu. This is to be used when the user's extended file description -// option is enabled (where the menu would take up about the left half of -// the screen). -// -// Parameters: -// pFileIdx: The index of the file metadata object in gFileList to use -// pStartScreenRow: Optional - The screen row number to start printing, for partial -// screen refreshing (can be in the middle of the extended description) -// pEndScreenRow: Optional - The screen row number to stop printing, for partial -// screen refreshing (can be in the middle of the extended description) -// pMaxWidth: Optional - The maximum width to use for printing the description lines -function displayFileExtDescOnMainScreen(pFileIdx, pStartScreenRow, pEndScreenRow, pMaxWidth) -{ - if (typeof(pFileIdx) !== "number") - return; - if (pFileIdx < 0 || pFileIdx >= gFileList.length) - return; - - // Get the file description from its metadata object - var fileMetadata = gFileList[pFileIdx]; - var fileDesc = ""; - if (fileMetadata.hasOwnProperty("extdesc") && fileMetadata.extdesc.length > 0) - fileDesc = fileMetadata.extdesc; - else - fileDesc = fileMetadata.desc; - if (typeof(fileDesc) != "string") - fileDesc = ""; - - // This might be overkill, but just in case, convert any non-Synchronet - // attribute codes to Synchronet attribute codes in the description. - // This will help simplify getting substrings for formatting. Then for - // efficiency, put the converted description back into the metadata - // object in the array so that it doesn't have to be converted again. - if (!fileMetadata.hasOwnProperty("attrsConverted")) - { - fileDesc = convertAttrsToSyncPerSysCfg(fileDesc); - fileMetadata.attrsConverted = true; - if (fileMetadata.hasOwnProperty("extdesc")) - fileMetadata.extdesc = fileDesc; - else - fileMetadata.desc = fileDesc; - } - - // Calculate where to write the description on the screen - var startX = gFileListMenu.size.width + 1; // Assuming the file menu starts at the leftmost column - var maxDescLen = console.screen_columns - startX; - if (typeof(pMaxWidth) === "number" && pMaxWidth >= 0 && pMaxWidth < maxDescLen) - maxDescLen = pMaxWidth; - // Go to the location on the screen and write the file description - var formatStr = "%-" + maxDescLen + "s"; - // firstScreenRow is the first row on the screen where the extended description - // should start at. lastScreenRow is the last row (inclusive) to use for - // printing the extended description - var firstScreenRow = gNumHeaderLinesDisplayed + 1; - var lastScreenRow = console.screen_rows - 1; // This is inclusive - // screenRowForPrinting will be used for the actual screen row we're at while - // printing the extended description lines - var screenRowForPrinting = firstScreenRow; - // If pStartScreenRow or pEndScreenRow are specified, then use - // them to specify the start & end screen rows to actually print - if (typeof(pStartScreenRow) === "number" && pStartScreenRow >= firstScreenRow && pStartScreenRow <= lastScreenRow) - screenRowForPrinting = pStartScreenRow; - if (typeof(pEndScreenRow) === "number" && pEndScreenRow > firstScreenRow && pStartScreenRow <= lastScreenRow) - lastScreenRow = pEndScreenRow; - var fileDescArray = fileDesc.split("\r\n"); - console.print("\x01n"); - // screenRowNum is to keep track of the row on the screen where the - // description line would be placed, in case the start row is after that - var screenRowNum = firstScreenRow; - for (var i = 0; i < fileDescArray.length; ++i) - { - if (screenRowForPrinting > screenRowNum++) - continue; - // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js - // Normally it would be handy to use printf() to print the text line: - //printf(formatStr, substrWithAttrCodes(fileDescArray[i], 0, maxDescLen)); - // However, printf() doesn't account for attribute codes and thus may not - // fill the rest of the width. So, we do that manually. - var descLine = substrWithAttrCodes(fileDescArray[i], 0, maxDescLen); - var lineTextLength = console.strlen(descLine); - if (lineTextLength > 0) - { - console.gotoxy(startX, screenRowForPrinting++); - console.print(descLine); - var remainingLen = maxDescLen - lineTextLength; - if (remainingLen > 0) - printf("%" + remainingLen + "s", ""); - } - // Stop printing the description lines when we reach the last line on - // the screen where we want to print. - if (screenRowForPrinting > lastScreenRow) - break; - } - // If there is room, shoe the file date on the next line - if (screenRowForPrinting <= lastScreenRow && fileMetadata.hasOwnProperty("time")) - { - console.print("\x01n"); - console.gotoxy(startX, screenRowForPrinting++); - var dateStr = "Date: " + strftime("%Y-%m-%d", fileMetadata.time); - printf("%-" + maxDescLen + "s", dateStr.substr(0, maxDescLen)); - } - // Clear the rest of the lines to the bottom of the list area - console.print("\x01n"); - while (screenRowForPrinting <= lastScreenRow) - { - console.gotoxy(startX, screenRowForPrinting++); - printf(formatStr, ""); - } -} - -// Refreshes (re-draws) the main content of the screen (file list menu, -// and extended description area if enabled). The coordinates are absolute -// screen coordinates. -// -// Parameters: -// pUpperLeftX: The X coordinate of the upper-left corner of the area to re-draw -// pUpperLeftY: The Y coordinate of the upper-left corner of the area to re-draw -// pWidth: The width of the area to re-draw -// pHeight: The height of the area to re-draw -// pSelectedItemIdxes: Optional: An object with selected item indexes for the file menu. -// If not passed, an empty object will be used. -// This can also be a boolean, and if true, will refresh the -// selected items on the file menu (with checkmarks) outside the -// given top & bottom screen rows. -function refreshScreenMainContent(pUpperLeftX, pUpperLeftY, pWidth, pHeight, pSelectedItemIdxes) -{ - // Have the file list menu partially re-draw itself if necessary - var startXWithinFileList = (pUpperLeftX >= gFileListMenu.pos.x && pUpperLeftX < gFileListMenu.pos.x + gFileListMenu.size.width); - var startYWithinFileList = (pUpperLeftY >= gFileListMenu.pos.y && pUpperLeftY < gFileListMenu.pos.y + gFileListMenu.size.height); - if (startXWithinFileList && startYWithinFileList) - { - var selectedItemIdxesIsValid = (typeof(pSelectedItemIdxes) === "object"); - var selectedItemIdxes = (selectedItemIdxesIsValid ? pSelectedItemIdxes : {}); - gFileListMenu.DrawPartialAbs(pUpperLeftX, pUpperLeftY, pWidth, pHeight, selectedItemIdxes); - } - // If pSelectedItemIdxes is a bool instead of an object and is true, - // refresh the selected items (with checkmarks) outside the top & bottom - // lines on the file menu - if (!selectedItemIdxesIsValid && typeof(pSelectedItemIdxes) === "boolean" && pSelectedItemIdxes && gFileListMenu.numSelectedItemIndexes() > 0) - { - var bottomScreenRow = pUpperLeftY + pHeight - 1; - for (var idx in gFileListMenu.selectedItemIndexes) - { - var idxNum = +idx; - var itemScreenRow = gFileListMenu.ScreenRowForItem(idxNum); - if (itemScreenRow == -1) - continue; - if (itemScreenRow < pUpperLeftY || itemScreenRow > bottomScreenRow) - { - var isSelected = (idxNum == gFileListMenu.selectedItemIdx); - gFileListMenu.WriteItemAtItsLocation(idxNum, isSelected, false); - } - } - } - // If the user has extended descriptions enabled, then the file menu - // is only taking up about half the screen on the left, and we'll also - // have to refresh the description area. - if (extendedDescEnabled()) - { - var fileMenuRightX = gFileListMenu.pos.x + gFileListMenu.size.width - 1; - var width = pWidth - (fileMenuRightX - pUpperLeftX + 1); - if (width > 0) - { - var firstRow = pUpperLeftY; - // The last row is inclusive. It seems like there might be an off-by-1 - // problem here? I thought 1 would need to be subtracted from lastRow - var lastRow = pUpperLeftY + pHeight; - // We don't want to overwrite the last row on the screen, since that's - // used for the command bar - if (lastRow == console.screen_rows) - --lastRow; - displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx, firstRow, lastRow, width); - } - } -} - -// Returns whether or not the lister is doing a file search -function isDoingFileSearch() -{ - return (gScriptMode == MODE_SEARCH_FILENAME || gScriptMode == MODE_SEARCH_DESCRIPTION || gScriptMode == MODE_NEW_FILE_SEARCH); -} - -// Custom file info sort function for upload time -function fileInfoSortULTime(pA, pB) -{ - if (pA.hasOwnProperty("added") && pB.hasOwnProperty("added")) - { - if (pA.added < pB.added) - return -1; - else if (pA.added > pB.added) - return 1; - else - return 0; - } - else - { - if (pA.time < pB.time) - return -1; - else if (pA.time > pB.time) - return 1; - else - return 0; - } -} - -// Custom file info sort function for download time -function fileInfoSortDLTime(pA, pB) -{ - if (pA.hasOwnProperty("last_downloaded") && pB.hasOwnProperty("last_downloaded")) - { - if (pA.last_downloaded < pB.last_downloaded) - return -1; - else if (pA.last_downloaded > pB.last_downloaded) - return 1; - else - return 0; - } - else - return 0; -} - -// Returns whether or not a user can download from a file directory, and writes an error if not. -// -// Parameters: -// pDirCode: The internal directory code of a file directory -// -// Return value: Boolean - Whether or not the user can download from the file directory given -function userCanDownloadFromFileArea_ShowErrorIfNot(pDirCode) -{ - var userCanDownload = bbs.compare_ars(file_area.dir[pDirCode].download_ars); - if (!userCanDownload) - { - // The user doesn't have permission to download from this directory - //file_area.dir[pDirCode].name - var areaFullDesc = file_area.dir[pDirCode].lib_name + ": " - + file_area.dir[pDirCode].description; - areaFullDesc = word_wrap(areaFullDesc, console.screen_columns-1, areaFullDesc.length).replace(/\r|\n/g, "\r\n"); - while (areaFullDesc.lastIndexOf("\r\n") == areaFullDesc.length-2) - areaFullDesc = areaFullDesc.substr(0, areaFullDesc.length-2); - console.crlf(); - console.print(areaFullDesc); - console.crlf(); - console.mnemonics(bbs.text(CantDownloadFromDir)); - console.crlf(); - console.pause(); - } - return userCanDownload; -} \ No newline at end of file