From fe7d801ab5830e0632b2eb3698a01aca700586b3 Mon Sep 17 00:00:00 2001 From: Eric Oulashin <eric.oulashin@gmail.com> Date: Sun, 13 Feb 2022 19:19:51 -0800 Subject: [PATCH] Digital Distortion File Lister version 2.02: Added the ability to do a file search (via filespec, description, or new files since last scan). A command-line parameter, -MODE, specifies which search to perform (search_filename, search_description, or new_file_search for searching; list_curdir lists files in the user's current directory, which is the default). --- exec/load/dd_lightbar_menu.js | 15 +- xtrn/ddfilelister/ddfilelister.js | 1419 +++++++++++++++++++----- xtrn/ddfilelister/readme.txt | 36 +- xtrn/ddfilelister/revision_history.txt | 6 + 4 files changed, 1206 insertions(+), 270 deletions(-) diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js index 9b1e5320dd..1c0e7e4d8d 100644 --- a/exec/load/dd_lightbar_menu.js +++ b/exec/load/dd_lightbar_menu.js @@ -724,7 +724,7 @@ function DDLightbarMenu_Draw(pSelectedItemIndexes, pDrawBorders, pDrawScrollbar) if (!this.drawnAlready) this.DisplayInitialScrollbar(this.pos.y); else - this.UpdateScrollbarWithHighlightedItem(); + this.UpdateScrollbarWithHighlightedItem(true); } // For numbered mode, we'll need to know the length of the longest item number // so that we can use that space to display the item numbers. @@ -1347,7 +1347,11 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) var draw = (typeof(pDraw) == "boolean" ? pDraw : true); if (draw) + { this.Draw(pSelectedItemIndexes); + if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow()) + this.DisplayInitialScrollbar(this.scrollbarInfo.solidBlockLastStartRow); + } // User input loop var userChoices = null; // For multi-select mode @@ -2326,10 +2330,15 @@ function DDLightbarMenu_CalcScrollbarSolidBlockStartRow() // Updates the scrollbar position based on the currently-selected // item index, this.selectedItemIdx. -function DDLightbarMenu_UpdateScrollbarWithHighlightedItem() +// +// Parameters: +// pForceUpdate: Boolean - Whether or not to force the redraw regardless of block location. +// Defaults to false. +function DDLightbarMenu_UpdateScrollbarWithHighlightedItem(pForceUpdate) { + var forceUpdate = (typeof(pForceUpdate) === "boolean" ? pForceUpdate : false); var solidBlockStartRow = this.CalcScrollbarSolidBlockStartRow(); - if (solidBlockStartRow != this.scrollbarInfo.solidBlockLastStartRow) + if (forceUpdate || (solidBlockStartRow != this.scrollbarInfo.solidBlockLastStartRow)) this.UpdateScrollbar(solidBlockStartRow, this.scrollbarInfo.solidBlockLastStartRow, this.scrollbarInfo.numSolidScrollBlocks); this.scrollbarInfo.solidBlockLastStartRow = solidBlockStartRow; } diff --git a/xtrn/ddfilelister/ddfilelister.js b/xtrn/ddfilelister/ddfilelister.js index 1c13b34d0e..1b089cebba 100644 --- a/xtrn/ddfilelister/ddfilelister.js +++ b/xtrn/ddfilelister/ddfilelister.js @@ -18,6 +18,10 @@ * 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). */ if (typeof(require) === "function") @@ -42,7 +46,6 @@ else // If the user's terminal doesn't support ANSI, then just call the standard Synchronet // file list function and exit now -// TODO: Create a traditional user interface? if (!console.term_supports(USER_ANSI)) { bbs.list_files(); @@ -50,16 +53,13 @@ if (!console.term_supports(USER_ANSI)) } -// Store whether the user is a sysop -var gUserIsSysop = user.compare_ars("SYSOP"); - // This script requires Synchronet version 3.19 or higher. // If the Synchronet version is below the minimum, then just call the standard // Synchronet file list and exit. if (system.version_num < 31900) { - if (gUserIsSysop) + if (user.is_sysop) { var message = "\1n\1h\1y\1i* Warning:\1n\1h\1w Digital Distortion File Lister " + "requires version \1g3.19\1w or\r\n" @@ -75,8 +75,8 @@ if (system.version_num < 31900) } // Lister version information -var LISTER_VERSION = "2.01"; -var LISTER_DATE = "2022-02-07"; +var LISTER_VERSION = "2.02"; +var LISTER_DATE = "2022-02-13"; /////////////////////////////////////////////////////////////////////////////// @@ -157,6 +157,15 @@ var QUIT = 5; var FILE_MOVE = 6; // Sysop action var FILE_DELETE = 7; // Sysop action +// Search/list modes +var MODE_LIST_CURDIR = 1; +var MODE_SEARCH_FILENAME = 2; +var MODE_SEARCH_DESCRIPTION = 3; +var MODE_NEW_FILE_SEARCH = 4; + +// The searc/list mode for the current run +var gScriptMode = MODE_LIST_CURDIR; // Default + // This will store the number of header lines that were displayed. This will control @@ -177,52 +186,49 @@ var gPauseAfterViewingFile = true; /////////////////////////////////////////////////////////////////////////////// // Script execution code -var gFilebase = new FileBase(bbs.curdir_code); -if (!gFilebase.open()) -{ - console.crlf(); - console.print("\1n\1h\1yUnable to open \1w" + file_area.dir[bbs.curdir_code].description + "\1n"); - console.crlf(); - console.pause(); - exit(1); -} - -// If we got here, the gFilebase successfully opened. -// If there are no files in the filebase, then say so and exit now. -if (gFilebase.files == 0) -{ - gFilebase.close(); - var libIdx = file_area.dir[bbs.curdir_code].lib_index; - console.crlf(); - console.print("\1n\1cThere are no files in \1h" + file_area.lib_list[libIdx].description + "\1n\1c - \1h" + - file_area.dir[bbs.curdir_code].description + "\1n"); - console.crlf(); - console.pause(); - exit(); -} - // 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; + // Read the configuration file and set the settings readConfigFile(); -// To check a user's file basic/extended detail information setting: -// if ((user.settings & USER_EXTDESC) == USER_EXTDESC) +// Parse command-line arguments (which sets program options) +parseArgs(argv); + +// This array will contain file metadata objects +var gFileList = []; + +// Populate the file list based on the script mode (list/search) +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("\1n\1c"); + if (gScriptMode == MODE_LIST_CURDIR) + console.print("There are no files in the current directory."); + else + console.print("No files were found."); + console.print("\1n"); + console.crlf(); + console.pause(); + exit(0); +} -// Get a list of file data with normal detail (without extended info). When the user -// selects a file to view extended info, we'll get metadata about the file with extended detail. -//var gFileList = gFilebase.get_list("*", FileBase.DETAIL.NORM); // FileBase.DETAIL.EXTENDED -var gFileList = gFilebase.get_list("*", FileBase.DETAIL.NORM, 0, true, gFileSortOrder); // FileBase.DETAIL.EXTENDED // Clear the screen and display the header lines console.clear("\1n"); -displayFileLibAndDirHeader(bbs.curdir_code); +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); +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({}); @@ -247,13 +253,22 @@ while (continueDoingFileList) { var currentActionVal = fileMenuBar.getCurrentSelectedAction(); fileMenuBar.setCurrentActionCode(currentActionVal); - actionRetObj = doAction(currentActionVal, bbs.curdir_code, gFilebase, gFileList, gFileListMenu); + actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); + } + // Allow the delete key as a special key for sysops to delete the selected file(s) + else if (lastUserInputUpper == KEY_DEL) + { + if (user.is_sysop) + { + fileMenuBar.setCurrentActionCode(FILE_DELETE, true); + actionRetObj = doAction(FILE_DELETE, gFileList, gFileListMenu); + } } else { var currentActionVal = fileMenuBar.getActionFromChar(lastUserInputUpper, false); fileMenuBar.setCurrentActionCode(currentActionVal, true); - actionRetObj = doAction(currentActionVal, bbs.curdir_code, gFilebase, gFileList, gFileListMenu); + actionRetObj = doAction(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. @@ -263,11 +278,16 @@ while (continueDoingFileList) continueDoingFileList = false; else { - if (actionRetObj.reDrawListerHeader) + if (actionRetObj.reDrawHeaderTextOnly) + { + console.print("\1n"); + displayFileLibAndDirHeader(true); // Will move the cursor where it needs to be + } + else if (actionRetObj.reDrawListerHeader) { console.print("\1n"); console.gotoxy(1, 1); - displayFileLibAndDirHeader(bbs.curdir_code); + displayFileLibAndDirHeader(); } if (actionRetObj.reDrawCmdBar) // Could call fileMenuBar.constructPromptText(); if needed fileMenuBar.writePromptLine(); @@ -324,7 +344,6 @@ while (continueDoingFileList) } } -gFilebase.close(); @@ -336,47 +355,42 @@ gFilebase.close(); // Parameters: // pActionCode: A code specifying an action to do. Must be one of the global // action codes. -// pDirCode: The internal code of the file directory -// pFilebase: A Filebase object representing the downloadable file directory. This -// is assumed to be open. // 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(pActionCode, pDirCode, pFilebase, pFileList, pFileListMenu) +function doAction(pActionCode, pFileList, pFileListMenu) { if (typeof(pActionCode) !== "number") return getDefaultActionRetObj(); - if (pFilebase == null || typeof(pFilebase) !== "object") - return getDefaultActionRetObj(); var retObj = null; switch (pActionCode) { case FILE_VIEW_INFO: - retObj = showFileInfo(pFilebase, pFileList, pFileListMenu); + retObj = showFileInfo(pFileList, pFileListMenu); break; case FILE_VIEW: - retObj = viewFile(pFilebase, pFileList, pFileListMenu); + retObj = viewFile(pFileList, pFileListMenu); break; case FILE_ADD_TO_BATCH_DL: - retObj = addSelectedFilesToBatchDLQueue(pDirCode, pFilebase, pFileList, pFileListMenu); + retObj = addSelectedFilesToBatchDLQueue(pFileList, pFileListMenu); break; case HELP: - retObj = displayHelpScreen(pDirCode, pFilebase); + retObj = displayHelpScreen(); break; case QUIT: retObj = getDefaultActionRetObj(); retObj.continueFileLister = false; break; case FILE_MOVE: // Sysop action - if (gUserIsSysop) - retObj = chooseFilebaseAndMoveFileToOtherFilebase_Lightbar(pDirCode, pFilebase, pFileList, pFileListMenu); + if (user.is_sysop) + retObj = chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu); break; case FILE_DELETE: // Sysop action - if (gUserIsSysop) - retObj = removeFileFromFilebase(pDirCode, pFilebase, pFileList, pFileListMenu); + if (user.is_sysop) + retObj = confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu); break; } @@ -390,6 +404,8 @@ function doAction(pActionCode, pDirCode, pFilebase, pFileList, pFileListMenu) // continueFileLister: Boolean - Whether or not the file lister should continue, or exit // reDrawFileListMenu: Boolean - Whether or not to re-draw the whole file list // 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: @@ -405,6 +421,7 @@ function getDefaultActionRetObj() continueFileLister: true, reDrawFileListMenu: false, reDrawListerHeader: false, + reDrawHeaderTextOnly: false, reDrawCmdBar: false, fileListPartialRedrawInfo: null, exitNow: false @@ -414,40 +431,59 @@ function getDefaultActionRetObj() // Shows extended information about a file to the user. // // Parameters: -// pFilebase: A Filebase object representing the downloadable file directory. This -// is assumed to be open. // 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 showFileInfo(pFilebase, pFileList, pFileListMenu) +function showFileInfo(pFileList, pFileListMenu) { 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; + var frameInnerWidth = frameWidth - 2; // Without borders // 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. - var extdFileInfo = pFilebase.get(pFileList[pFileListMenu.selectedItemIdx], FileBase.DETAIL.EXTENDED); + // The metadata object in pFileList should have a dirCode added by this script. + // If not, assume the user's current directory. + var dirCode = bbs.curdir_code; + if (pFileList[pFileListMenu.selectedItemIdx].hasOwnProperty("dirCode")) + dirCode = pFileList[pFileListMenu.selectedItemIdx].dirCode; + var fileInfoObj = getFileInfoFromFilebase(dirCode, pFileList[pFileListMenu.selectedItemIdx].name, FileBase.DETAIL.EXTENDED); + var extdFileInfo = fileInfoObj.fileMetadataObj; + if (typeof(extdFileInfo) !== "object") + { + displayMsg("Unable to get file info!", true, true); + return; + } + var fileTime = fileInfoObj.fileTime; // Build a string with the file information - var fileTime = pFilebase.get_time(extdFileInfo.name); // Make sure the displayed filename isn't too crazy long - var adjustedFilename = shortenFilename(extdFileInfo.name, frameWidth-2, false); + var adjustedFilename = shortenFilename(extdFileInfo.name, frameInnerWidth, false); var fileInfoStr = "\1n\1wFilename"; if (adjustedFilename.length < extdFileInfo.name.length) fileInfoStr += " (shortened)"; fileInfoStr += ":\r\n"; fileInfoStr += gColors.filename + adjustedFilename + "\1n\1w\r\n"; - // Note: File size can also be retrieved by calling pFilebase.get_size(extdFileInfo.name) + // Note: File size can also be retrieved by calling a FileBase's get_size(extdFileInfo.name) // TODO: Shouldn't need the max length here fileInfoStr += "Size: " + gColors.fileSize + getFileSizeStr(extdFileInfo.size, 99999) + "\1n\1w\r\n"; fileInfoStr += "Timestamp: " + gColors.fileTimestamp + strftime("%Y-%m-%d %H:%M:%S", fileTime) + "\1n\1w\r\n" fileInfoStr += "\r\n"; - fileInfoStr += gColors.desc; + + // 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 += "\1c\1hLib\1g: \1n\1c" + libDesc.substr(0, frameInnerWidth-5) + "\1n\1w\r\n"; + fileInfoStr += "\1c\1hDir\1g: \1n\1c" + dirDesc.substr(0, frameInnerWidth-5) + "\1n\1w\r\n"; + fileInfoStr += "\r\n"; + // extdFileInfo should have extdDesc, but check just in case var fileDesc = ""; if (extdFileInfo.hasOwnProperty("extdesc") && extdFileInfo.extdesc.length > 0) @@ -458,10 +494,31 @@ function showFileInfo(pFilebase, pFileList, pFileListMenu) // so make sure it's a string if (typeof(fileDesc) !== "string") fileDesc = ""; + fileInfoStr += gColors.desc; if (fileDesc.length > 0) fileInfoStr += "Description:\r\n" + fileDesc; else fileInfoStr += "No description available"; + if (user.is_sysop) + { + var sysopFields = [ "from", "cost", "added"]; + for (var sI = 0; sI < sysopFields.length; ++sI) + { + var prop = sysopFields[sI]; + if (extdFileInfo.hasOwnProperty(prop)) + { + if (typeof(extdFileInfo[prop]) === "string" && extdFileInfo[prop].length == 0) + continue; + var propName = prop.charAt(0).toUpperCase() + prop.substr(1); + fileInfoStr += "\r\n\1n\1c\1h" + propName + "\1g:\1n\1c "; + if (prop == "added") + fileInfoStr += strftime("%Y-%m-%d %H:%M:%S", extdFileInfo.added); + else + fileInfoStr += extdFileInfo[prop].toString().substr(0, frameInnerWidth); + fileInfoStr += "\1n\1w"; + } + } + } fileInfoStr += "\1n\1w"; // Construct & draw a frame with the file information & do the input loop @@ -490,18 +547,30 @@ function showFileInfo(pFilebase, pFileList, pFileListMenu) // Lets the user view a file. // // Parameters: -// pFilebase: A Filebase object representing the downloadable file directory. This -// is assumed to be open. // 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 viewFile(pFilebase, pFileList, pFileListMenu) +function viewFile(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); - var fullyPathedFilename = pFilebase.get_path(pFileList[pFileListMenu.selectedItemIdx]); + // Open the filebase & get the fully pathed filename + var fullyPathedFilename = ""; + var filebase = new FileBase(pFileList[pFileListMenu.selectedItemIdx].dirCode); + if (filebase.open()) + { + fullyPathedFilename = filebase.get_path(pFileList[pFileListMenu.selectedItemIdx]); + filebase.close(); + } + else + { + displayMsg("Failed to open the filebase!", true, true); + return retObj; + } + + // View the file console.gotoxy(1, console.screen_rows); console.print("\1n"); console.crlf(); @@ -519,14 +588,12 @@ function viewFile(pFilebase, pFileList, pFileListMenu) // Allows the user to add their selected file to their batch downloaded queue // // Parameters: -// pDirCode: The internal code of the file directory -// pFilebase: The FileBase object representing the file directory (assumed open) // 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 addSelectedFilesToBatchDLQueue(pDirCode, pFilebase, pFileList, pFileListMenu) +function addSelectedFilesToBatchDLQueue(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); @@ -572,8 +639,9 @@ function addSelectedFilesToBatchDLQueue(pDirCode, pFilebase, pFileList, pFileLis batchDLFile.writeln(""); // Add the required "dir" and "desc" properties to the user's batch download - // queue file. The section is the filename. - addToQueueSuccessful = batchDLFile.iniSetValue(metadataObjects[i].name, "dir", pDirCode); + // 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); @@ -642,7 +710,6 @@ function addSelectedFilesToBatchDLQueue(pDirCode, pFilebase, pFileList, pFileLis // download their batch DL queue var frameTitle = "Download your batch queue (Y/N)?"; // \1cFiles: \1h1 \1n\1c(\1h100 \1n\1cMax) Credits: 0 Bytes: \1h2,228,254 \1n\1c Time: 00:09:40 - //var fileSize = gFilebase.get_size(gFileList[pIdx].name); // Note: The maximum number of allowed files in the batch download queue doesn't seem to // be available to JavaScript. var totalQueueSize = batchDLQueueStats.totalSize + pFileList[pFileListMenu.selectedItemIdx].size; @@ -786,12 +853,7 @@ function getUserDLQueueStats() } // Displays the help screen. -// -// Parameters: -// pDirCode: The internal code of the file directory being used -// pFilebase: A Filebase object representing the downloadable file directory. This -// is assumed to be open. -function displayHelpScreen(pDirCode, pFilebase) +function displayHelpScreen() { var retObj = getDefaultActionRetObj(); @@ -801,14 +863,23 @@ function displayHelpScreen(pDirCode, pFilebase) console.center("\1n\1cVersion \1g" + LISTER_VERSION + " \1w\1h(\1b" + LISTER_DATE + "\1w)"); console.crlf(); - // Display information about the current file directory - var libIdx = file_area.dir[pDirCode].lib_index; - var dirIdx = file_area.dir[pDirCode].index; - console.print("\1n\1cCurrent file library: \1g" + file_area.lib_list[libIdx].description); - console.crlf(); - console.print("\1cCurrent file directory: \1g" + file_area.dir[pDirCode].description); - console.crlf(); - console.print("\1cThere are \1g" + pFilebase.files + " \1cfiles in this directory."); + // If listing files in a directory, display information about the current file directory. + if (gScriptMode == MODE_LIST_CURDIR) + { + var libIdx = file_area.dir[bbs.curdir_code].lib_index; + var dirIdx = file_area.dir[bbs.curdir_code].index; + console.print("\1n\1cCurrent file library: \1g" + file_area.lib_list[libIdx].description); + console.crlf(); + console.print("\1cCurrent file directory: \1g" + file_area.dir[bbs.curdir_code].description); + console.crlf(); + console.print("\1cThere are \1g" + file_area.dir[bbs.curdir_code].files + " \1cfiles in this directory."); + } + else if (gScriptMode == MODE_SEARCH_FILENAME) + console.print("\1n\1cCurrently performing a filename search"); + else if (gScriptMode == MODE_SEARCH_DESCRIPTION) + console.print("\1n\1cCurrently performing a description search"); + else if (gScriptMode == MODE_NEW_FILE_SEARCH) + console.print("\1n\1cCurrently performing a new file search"); console.crlf(); console.crlf(); @@ -817,7 +888,7 @@ function displayHelpScreen(pDirCode, pFilebase) 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 (gUserIsSysop) + 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 "; @@ -834,7 +905,7 @@ function displayHelpScreen(pDirCode, pFilebase) printf(printfStr, "I", "Display extended file information"); printf(printfStr, "V", "View the file"); printf(printfStr, "B", "Flag the file(s) for batch download"); - if (gUserIsSysop) + if (user.is_sysop) { printf(printfStr, "M", "Move the file(s) to another directory"); printf(printfStr, "D", "Delete the file(s)"); @@ -854,23 +925,21 @@ function displayHelpScreen(pDirCode, pFilebase) // Allows the user to move the selected file to another filebase. Only for sysops! // // Parameters: -// pDirCode: The internal code of the original file directory -// pFilebase: The FileBase object representing the file directory (assumed open) // 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_Lightbar(pDirCode, pFilebase, pFileList, pFileListMenu) +function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); // Confirm with the user to move the file(s). If they don't want to, // then just return now. var filenames = []; - if (gFileListMenu.numSelectedItemIndexes() > 0) + if (pFileListMenu.numSelectedItemIndexes() > 0) { - for (var idx in gFileListMenu.selectedItemIndexes) + for (var idx in pFileListMenu.selectedItemIndexes) filenames.push(pFileList[+idx].name); } else @@ -882,23 +951,34 @@ function chooseFilebaseAndMoveFileToOtherFilebase_Lightbar(pDirCode, pFilebase, return retObj; - retObj.reDrawFileListMenu = true; - // Prompt the user which directory to move the file to - var chosenDirCode = null; + // 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, + 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("\1n\1c\1h|\1n\1c%-" + +(fileLibMenu.size.width-1) + "s\1n", "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 != pDirCode) + if (chosenDirCode != pFileList[pFileListMenu.selectedItemIdx].dirCode) continueOn = false; else { @@ -913,68 +993,178 @@ function chooseFilebaseAndMoveFileToOtherFilebase_Lightbar(pDirCode, pFilebase, // 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 (gFileListMenu.numSelectedItemIndexes() > 0) + if (pFileListMenu.numSelectedItemIndexes() > 0) { - for (var idx in gFileListMenu.selectedItemIndexes) + for (var idx in pFileListMenu.selectedItemIndexes) fileIndexes.push(+idx); } else fileIndexes.push(+(pFileListMenu.selectedItemIdx)); - fileIndexes.sort(); + // 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]; - var moveRetObj = moveFileToOtherFilebase(pFilebase, pFileList[fileIdx], chosenDirCode); + // 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) { - // Remove the file info object from the file list array - 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; + 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_CURDIR) + { + 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.reDrawFileListMenu = true; + } + else + { + // Note: getFileInfoFromFilebase() will add dirCode to the metadata object + var fileDataObj = getFileInfoFromFilebase(chosenDirCode, pFileList[fileIdx].name, FileBase.DETAIL.NORM); + pFileList[fileIdx] = fileDataObj.fileMetadataObj; + /* + // 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 (typeof(pFileList.allSameDir) == "boolean") + { + if (pFileList.allSameDir) + { + if (pFileList.length > 1) + pFileList.allSameDir = false; + //retObj.reDrawListerHeader = true; + 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.reDrawListerHeader = pFileList.allSameDir; + retObj.reDrawHeaderTextOnly = pFileList.allSameDir; + } + } + */ + } } else { moveAllSucceeded = false; - displayMsg(pFileList[fileIdx].name, true); - displayMsg(moveRetObj.failReason, true); + 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_CURDIR && typeof(pFileList.allSameDir) == "boolean") + { + if (pFileList.allSameDir) + { + if (pFileList.length > 1) + pFileList.allSameDir = false; + //retObj.reDrawListerHeader = true; + 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.reDrawListerHeader = pFileList.allSameDir; + retObj.reDrawHeaderTextOnly = pFileList.allSameDir; + } + } + // Display a success/fail message if (moveAllSucceeded) { - var libIdx = file_area.dir[chosenDirCode].lib_index; - var msg = "Successfully moved the file(s) to " - + file_area.lib_list[libIdx].description + " - " - + file_area.dir[chosenDirCode].description - displayMsg(msg, false); + var msg = "Successfully moved the file(s) to " + destLibAndDirDesc; + displayMsg(msg, false, true); } - // After moving the files, if the file directory is empty, say so - if (pFilebase.files == 0) + else { - displayMsg("The directory now has no files.", false); + 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_CURDIR && file_area.dir[bbs.curdir_code].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; + } + else + { + // The user has canceled out of the area selection. + // We'll want to re-draw part of the file list to erase the area chooser menu. + retObj.fileListPartialRedrawInfo = fileListPartialRedrawInfo; } return retObj; } -// Allows the user to remove the selected file from the filebase. Only for sysops! +// Allows the user to remove the selected file(s) from the filebase. Only for sysops! // // Parameters: -// pDirCode: The internal code of the original file directory -// pFilebase: The FileBase object representing the file directory (assumed open) // 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 removeFileFromFilebase(pDirCode, pFilebase, pFileList, pFileListMenu) +function confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); @@ -982,9 +1172,9 @@ function removeFileFromFilebase(pDirCode, pFilebase, pFileList, pFileListMenu) // If there are multiple selected files, then prompt to remove each of them. // Otherwise, prompt for the one selected file. var filenames = []; - if (gFileListMenu.numSelectedItemIndexes() > 0) + if (pFileListMenu.numSelectedItemIndexes() > 0) { - for (var idx in gFileListMenu.selectedItemIndexes) + for (var idx in pFileListMenu.selectedItemIndexes) filenames.push(pFileList[+idx].name); } else @@ -994,33 +1184,126 @@ function removeFileFromFilebase(pDirCode, pFilebase, pFileList, pFileListMenu) var removeFilesConfirmed = confirmFileActionWithUser(filenames, "Remove", false); if (removeFilesConfirmed) { - // FileBase.remove(filename [,delete=false]) - var succeeded = pFilebase.remove(pFileList[pFileListMenu.selectedItemIdx].name, true); - if (succeeded) + 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 messages = [ "Successfully removed the file(s)." ]; - // Remove the file info object from the file list array - pFileList.splice(pFileListMenu.selectedItemIdx, 1); - // Adjust the file list menu's current selected index - --pFileListMenu.selectedItemIdx; - if (pFileListMenu.selectedItemIdx < 0) - pFileListMenu.selectedItemIdx = 0; - if (pFileListMenu.topItemIdx > pFileListMenu.selectedItemIdx) - pFileListMenu.topItemIdx = pFileListMenu.selectedItemIdx; - // If the file directory 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. - if (pFilebase.files > 0) - retObj.reDrawFileListMenu = true; + 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()) + { + // FileBase.remove(filename [,delete=false]) + removeFileSucceeded = filebase.remove(pFileList[fileIdx].name, true); + if (gScriptMode == MODE_LIST_CURDIR) + numFilesRemaining = filebase.files; + filebase.close(); + } else + removeAllSucceeded = false; + + // Log a success/error message + var logMsg = ""; + var logLevel = LOG_INFO; + if (removeFileSucceeded) { - messages.push("The directory now has no files."); - retObj.exitNow = true; + 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.reDrawFileListMenu = 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; + } } - displayMsgs(messages, false); + // 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 - displayMsg("Failed to remove the file!", true); // console.print("\1y\1hFailed to remove the file!\1n"); + { + if (gScriptMode == MODE_LIST_CURDIR) + 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; @@ -1065,7 +1348,7 @@ function DDFileMenuBar(pPos) 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)); - if (gUserIsSysop) + if (user.is_sysop) { this.cmdArray.push(new DDFileMenuBarItem("Move", 0, FILE_MOVE)); this.cmdArray.push(new DDFileMenuBarItem("Del", 0, FILE_DELETE)); @@ -1322,17 +1605,53 @@ function DDFileMenuBarItem(pItemText, pPos, pRetCode) /////////////////////////////////////////////////////////////////////////////// // 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) +{ + var retObj = { + fileMetadataObj: null, + fileTime: 0 + }; + + if (typeof(pDirCode) !== "string" || pDirCode.length == 0 || typeof(pFilename) !== "string" || pFilename.length == 0) + return retObj; + + 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); + retObj.fileMetadataObj = filebase.get(filename, fileDetail); + retObj.fileMetadataObj.dirCode = pDirCode; + //retObj.fileMetadataObj.size = filebase.get_size(filename); + retObj.fileTime = filebase.get_time(filename); + filebase.close(); + } + + return retObj; +} + // Moves a file from one filebase to another // // Parameters: -// pSrcFilebase: A FileBase object representing the source filebase. This is assumed to be open. // 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(pSrcFilebase, pSrcFileMetadata, pDestDirCode) +function moveFileToOtherFilebase(pSrcFileMetadata, pDestDirCode) { var retObj = { moveSucceeded: false, @@ -1342,37 +1661,51 @@ function moveFileToOtherFilebase(pSrcFilebase, pSrcFileMetadata, pDestDirCode) // 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 extdFileInfo = pSrcFilebase.get(pSrcFileMetadata, FileBase.DETAIL.MAX); - // Move the file over, remove it from the original filebase, and add it to the new filebase - var srcFilenameFull = pSrcFilebase.get_path(pSrcFileMetadata); - var destFilenameFull = file_area.dir[pDestDirCode].path + pSrcFileMetadata.name; - if (file_rename(srcFilenameFull, destFilenameFull)) + var srcFilebase = new FileBase(pSrcFileMetadata.dirCode); + if (srcFilebase.open()) { - if (pSrcFilebase.remove(pSrcFileMetadata.name, false)) + 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)) { - // Add the file to the other directory - var destFilebase = new FileBase(pDestDirCode); - if (destFilebase.open()) + if (srcFilebase.remove(pSrcFileMetadata.name, false)) { - retObj.moveSucceeded = destFilebase.add(extdFileInfo); - destFilebase.close(); + // 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 open the destination filebase"; - // Try to add the file back to the source filebase - var moveBackSucceeded = false; - if (file_rename(destFilenameFull, srcFilenameFull)) - moveBackSucceeded = pSrcFilebase.add(extdFileInfo); - if (!moveBackSucceeded) - retObj.failReason += " & moving the file back failed"; - } + retObj.failReason = "Failed to remove the file from the source directory"; } else - retObj.failReason = "Failed to remove the file from the source directory"; + retObj.failReason = "Failed to move the file to the new filebase directory"; + + srcFilebase.close(); } else - retObj.failReason = "Failed to move the file to the new filebase directory"; + { + 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; } @@ -1523,44 +1856,87 @@ function doFrameInputLoop(pFrame, pScrollbar, pFrameContentStr, pAdditionalQuitK // Displays the header lines for showing above the file list // // Parameters: -// pDirCode: The internal code of the file directory to use -function displayFileLibAndDirHeader(pDirCode) +// pTextOnly: Only draw the library & directory text (no decoration or other text). +// This is optional & defaults to false. +function displayFileLibAndDirHeader(pTextOnly) { - if (typeof(pDirCode) !== "string") - return; - if (typeof(file_area.dir[pDirCode]) === "undefined") - return; + var textOnly = (typeof(pTextOnly) === "boolean" ? pTextOnly : false); - var libIdx = file_area.dir[pDirCode].lib_index; - var dirIdx = file_area.dir[pDirCode].index; - var libDesc = file_area.lib_list[libIdx].description; - var dirDesc = file_area.dir[pDirCode].description; + // 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_CURDIR) + dirCode = bbs.curdir_code; + 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 + { + libIdx = -1; + dirIdx = -1; + libDesc = "Various"; + dirDesc = "Various"; + } var hdrTextWidth = console.screen_columns - 21; var descWidth = hdrTextWidth - 11; + var libText = format("\1cLib \1w\1h#\1b%4d\1c: \1n\1c%-" + descWidth + "s\1n", +(libIdx+1), libDesc.substr(0, descWidth)); + var dirText = format("\1cDir \1w\1h#\1b%4d\1c: \1n\1c%-" + descWidth + "s\1n", +(dirIdx+1), dirDesc.substr(0, descWidth)); // Library line - console.print("\1n\1w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4 + THIN_RECTANGLE_LEFT); - printf("\1cLib \1w\1h#\1b%4d\1c: \1n\1c%-" + descWidth + "s\1n", +(libIdx+1), libDesc.substr(0, descWidth)); - console.print("\1w" + THIN_RECTANGLE_RIGHT + "\1k\1h" + BLOCK4 + "\1n\1w" + THIN_RECTANGLE_LEFT + - "\1g\1hDD File\1n\1w"); - console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); - console.crlf(); - // Directory line - console.print("\1n\1w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4 + THIN_RECTANGLE_LEFT); - printf("\1cDir \1w\1h#\1b%4d\1c: \1n\1c%-" + descWidth + "s\1n", +(dirIdx+1), dirDesc.substr(0, descWidth)); - console.print("\1w" + THIN_RECTANGLE_RIGHT + "\1k\1h" + BLOCK4 + "\1n\1w" + THIN_RECTANGLE_LEFT + - "\1g\1hLister \1n\1w"); - console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); - console.print("\1n"); - gNumHeaderLinesDisplayed = 2; + if (textOnly) + { + console.gotoxy(6, 1); + console.print("\1n" + libText); + console.gotoxy(6, 2); + console.print("\1n" + dirText); + } + else + { + console.print("\1n\1w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4 + THIN_RECTANGLE_LEFT); + console.print(libText); + console.print("\1w" + THIN_RECTANGLE_RIGHT + "\1k\1h" + BLOCK4 + "\1n\1w" + THIN_RECTANGLE_LEFT + + "\1g\1hDD File\1n\1w"); + console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); + console.crlf(); + // Directory line + console.print("\1n\1w" + BLOCK1 + BLOCK2 + BLOCK3 + BLOCK4 + THIN_RECTANGLE_LEFT); + console.print(dirText); + console.print("\1w" + THIN_RECTANGLE_RIGHT + "\1k\1h" + BLOCK4 + "\1n\1w" + THIN_RECTANGLE_LEFT + + "\1g\1hLister \1n\1w"); + console.print(THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1); + console.print("\1n"); - // List header - console.crlf(); - displayListHdrLine(false); - ++gNumHeaderLinesDisplayed; + // List header + console.crlf(); + displayListHdrLine(false); - gErrorMsgBoxULY = gNumHeaderLinesDisplayed; // Note: console.screen_rows is 1-based + 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 // @@ -1572,7 +1948,10 @@ function displayListHdrLine(pMoveToLocationFirst) console.gotoxy(1, 3); var filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; var fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; - var shortDescLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; + //var shortDescLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; + // shortDescLen here should always be the same (for the last blocks to always be in the same + // position), whereas descriptionEnd might change based on whether the menu is using its scrollbar + var shortDescLen = 60; var formatStr = "\1n\1w\1h%-" + filenameLen + "s %" + fileSizeLen + "s %-" + +(shortDescLen-7) + "s\1n\1w%5s\1n"; var listHdrEndText = THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1; @@ -1603,36 +1982,60 @@ function createFileListMenu(pQuitKeys) if (typeof(pQuitKeys) === "string") fileListMenu.AddAdditionalQuitKeys(pQuitKeys); - fileListMenu.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}] - }); - - fileListMenu.filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; - fileListMenu.fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; - fileListMenu.shortDescLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; - fileListMenu.fileFormatStr = "%-" + fileListMenu.filenameLen - + "s %" + fileListMenu.fileSizeLen - + "s %-" + fileListMenu.shortDescLen + "s"; - - // Define the menu functions for getting the number of items and getting an item + // 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() { - // could also return gFilebase.files 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; + 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(); + + // Define the menu function for getting an item fileListMenu.GetItem = function(pIdx) { var menuItemObj = this.MakeItemWithRetval(pIdx); var filename = shortenFilename(gFileList[pIdx].name, this.filenameLen, true); - // Note: The file size is in bytes - var fileSize = gFilebase.get_size(gFileList[pIdx].name); + // 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,//gFileList[pIdx].name.substr(0, this.filenameLen), - getFileSizeStr(fileSize, this.fileSizeLen), + filename, + getFileSizeStr(gFileList[pIdx].size, this.fileSizeLen), desc.substr(0, this.shortDescLen)); return menuItemObj; } @@ -1675,11 +2078,20 @@ function createFileLibMenu() 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"; @@ -1702,10 +2114,7 @@ function createFileLibMenu() }); fileLibMenu.topBorderText = "\1y\1hFile Libraries"; - // Define the menu functions for getting the number of items and getting an item - fileLibMenu.NumItems = function() { - return file_area.lib_list.length; - }; + // Define the menu function for getting an item fileLibMenu.GetItem = function(pIdx) { var menuItemObj = this.MakeItemWithRetval(pIdx); menuItemObj.text = format(this.libFormatStr, @@ -1747,11 +2156,7 @@ function createFileDirMenu(pLibIdx) // Make sure there are directories in this library if (file_area.lib_list[pLibIdx].dir_list.length == 0) { - // TODO: Better error display - console.gotoxy(5, startRow); - console.print("\1n\1y\1hThere are no directories in this file library \1n"); - console.crlf(); - console.pause(); + displayMsg("There are no directories in this file library", true, true); return null; } @@ -1769,12 +2174,21 @@ function createFileDirMenu(pLibIdx) 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"; @@ -1797,47 +2211,20 @@ function createFileDirMenu(pLibIdx) }); fileDirMenu.topBorderText = "\1y\1h" + ("File directories of " + file_area.lib_list[pLibIdx].description).substr(0, fileDirMenu.size.width-2); - // Define the menu functions for getting the number of items and getting an item - fileDirMenu.NumItems = function() { - return file_area.lib_list[this.libIdx].dir_list.length; - }; + // 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), - getNumFilesInDir(this.libIdx, pIdx)); + file_area.lib_list[this.libIdx].dir_list[pIdx].files); return menuItemObj; } return fileDirMenu; } -// Returns the number of files in a file directory -// -// Parameters: -// pLibIdx: The library index -// pDirIdx: The directory index within the file library -// -// Return value: The number of files in the file directory -function getNumFilesInDir(pLibIdx, pDirIdx) -{ - if (typeof(pLibIdx) !== "number" || typeof(pDirIdx) !== "number") - return 0; - if (pLibIdx < 0 || pLibIdx >= file_area.lib_list.length) - return 0; - if (pDirIdx < 0 || pDirIdx >= file_area.lib_list[pLibIdx].dir_list.length) - return 0; - - var numFiles = 0; - var filebase = new FileBase(file_area.lib_list[pLibIdx].dir_list[pDirIdx].code); - if (filebase.open()) - { - numFiles = filebase.files; - filebase.close(); - } - return numFiles; -} + // Returns the largest number of files in all directories in a file library // // Parameters: @@ -1849,7 +2236,7 @@ function getLargestNumFilesInLibDirs(pLibIdx) var largestNumFiles = 0; for (var dirIdx = 0; dirIdx < file_area.lib_list[pLibIdx].dir_list.length; ++dirIdx) { - var numFilesInDir = getNumFilesInDir(pLibIdx, dirIdx); + var numFilesInDir = file_area.lib_list[pLibIdx].dir_list[dirIdx].files; if (numFilesInDir > largestNumFiles) largestNumFiles = numFilesInDir; } @@ -2548,3 +2935,507 @@ function shortenFilename(pFilename, pMaxLen, pFillWidth) return adjustedFilename; } + + +// 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: +// pArgArr: An array of strings containing values in the format -arg=val +function parseArgs(pArgArr) +{ + // Default program options + gScriptMode = MODE_LIST_CURDIR; + + // Sanity checking for pArgArr - Make sure it's an array + if ((typeof(pArgArr) != "object") || (typeof(pArgArr.length) != "number")) + return; + + // Go through pArgArr 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 < pArgArr.length; ++i) + { + // We're looking for strings that start with "-", except strings that are + // only "-". + if ((typeof(pArgArr[i]) != "string") || (pArgArr[i].length == 0) || + (pArgArr[i].charAt(0) != "-") || (pArgArr[i] == "-")) + { + continue; + } + + // Look for an = and if found, split the string on the = + equalsIdx = pArgArr[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 = pArgArr[i].substring(1, equalsIdx).toUpperCase(); + argVal = pArgArr[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_CURDIR; + } + } + else // An equals sign (=) was not found. Add as a boolean set to true to enable the option. + { + // Nothing to be done here for this script + } + } +} + +// 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_CURDIR) // This is the default + { + var filebase = new FileBase(bbs.curdir_code); + 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[bbs.curdir_code].lib_index; + console.crlf(); + console.print("\1n\1cThere are no files in \1h" + file_area.lib_list[libIdx].description + "\1n\1c - \1h" + + file_area.dir[bbs.curdir_code].description + "\1n"); + console.crlf(); + console.pause(); + retObj.exitNow = true; + retObj.exitCode = 0; + return retObj; + } + + // To check a user's file basic/extended detail information setting: + // if ((user.settings & USER_EXTDESC) == USER_EXTDESC) + + // Get a list of file data with normal detail (without extended info). When the user + // selects a file to view extended info, we'll get metadata about the file with extended detail. + gFileList = filebase.get_list("*", FileBase.DETAIL.NORM, 0, true, gFileSortOrder); // FileBase.DETAIL.EXTENDED + filebase.close(); + // Add a dirCode property to the file metadata objects (for consistency, + // as file search results may contain files from multiple directories). + for (var i = 0; i < gFileList.length; ++i) + gFileList[i].dirCode = bbs.curdir_code; + } + else + { + console.crlf(); + console.print("\1n\1h\1yUnable to open \1w" + file_area.dir[bbs.curdir_code].description + "\1n"); + console.crlf(); + console.pause(); + retObj.exitNow = true; + retObj.exitCode = 1; + return retObj; + } + } + else if (pSearchMode == MODE_SEARCH_FILENAME) + { + var lastDirCode = ""; + + // Prompt the user for directory, library, or all + console.print("\1n"); + console.crlf(); + console.mnemonics(bbs.text(DirLibOrAll)); + var validInputOptions = "DLA"; + var 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 = ""; + + // Prompt the user for directory, library, or all + console.print("\1n"); + console.crlf(); + //console.print("\r\n\1c\1hFind Text in File Descriptions (no wildcards)\1n\r\n"); + console.mnemonics(bbs.text(DirLibOrAll)); + console.print("\1n"); + var validInputOptions = "DLA"; + var 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. + */ + + // Prompt the user for directory, library, or all + console.print("\1n"); + console.crlf(); + console.mnemonics(bbs.text(DirLibOrAll)); + var validInputOptions = "DLA"; + var userInputDLA = console.getkeys(validInputOptions, -1, K_UPPER); + console.print("\1n"); + console.crlf(); + if (userInputDLA == "D" || userInputDLA == "L" || userInputDLA == "A") + { + console.print("\1n\1cSearching for files uploaded after \1h" + system.timestr(bbs.new_file_time) + "\1n"); + 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_CURDIR) + gFileList.allSameDir = allSameDir; + + if (dirErrors.length > 0) + { + console.print("\1n\1y\1h"); + for (var i = 0; i < dirErrors.length; ++i) + { + console.print(dirErrors[i]); + console.crlf(); + } + console.print("\1n"); + console.pause(); + retObj.exitNow = true; + retObj.exitCode = 1; + return retObj; + } + + 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 fileList = filebase.get_list(pFilespec, FileBase.DETAIL.NORM, 0, true, gFileSortOrder); // Or EXTENDED + retObj.foundFiles = (fileList.length > 0); + filebase.close(); + for (var i = 0; i < fileList.length; ++i) + { + 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("*", 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")) + { + 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 fileList = filebase.get_list("*", FileBase.DETAIL.NORM, 0, true, gFileSortOrder); + filebase.close(); + for (var i = 0; i < fileList.length; ++i) + { + if (fileList[i].added >= pSinceTime) + { + 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 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(bbs.curdir_code); + 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[bbs.curdir_code].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(); + } + 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(); + } + 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 : "")); + } + + return retObj; +} \ No newline at end of file diff --git a/xtrn/ddfilelister/readme.txt b/xtrn/ddfilelister/readme.txt index 46d30fe5fa..e3ea56422c 100644 --- a/xtrn/ddfilelister/readme.txt +++ b/xtrn/ddfilelister/readme.txt @@ -1,6 +1,6 @@ Digital Distortion File Lister - Version 2.01 - Release date: 2022-02-07 + Version 2.02 + Release date: 2022-02-13 by @@ -22,6 +22,7 @@ Contents - Command shell setup - Background: Running JavaScript scripts in Synchronet 4. Configuration file & color/text theme configuration file +5. Strings used from text.dat 1. Disclaimer @@ -66,6 +67,13 @@ Synchronet's viewable files configuration), and adding files to the user's batch download queue. Additionally, sysops can delete files and move files to another file directory. +In addition to listing files in the user's current directory, this lister can +perform a file search (via filespec, description, or new file search since last +search). The default is to list files in the current directory, but a search +mode can be specified with the command-line option -MODE. search_filename, +search_description, or new_file_search will perform the searching; list_curdir +lists files in the user's current directory, which is the default. + 3. Installation & Setup ======================= @@ -115,6 +123,18 @@ In a JavaScript script, you can use the bbs.exec() function to run a JavaScript script, as in the following example: bbs.exec("?../xtrn/ddfilelister/ddfilelister.js"); +To perform searching, you can add the -MODE option on the command line. +To do a filename search: +?../xtrn/DigDist/ddfilelister/ddfilelister.js -MODE=search_filename +To do a description search: +?../xtrn/DigDist/ddfilelister/ddfilelister.js -MODE=search_description +To search for new files since the last search: +?../xtrn/DigDist/ddfilelister/ddfilelister.js -MODE=new_file_search +You can also specify a mode to list the user's current directory, which is +already the default action: +?../xtrn/DigDist/ddfilelister/ddfilelister.js -MODE=list_curdir + + To install the file lister as an external program (in SCFG in External Programs > Online Programs (Doors)), see the following document for more information: @@ -248,4 +268,14 @@ fileAreaDescHighlight The file library/directory description for 'highlight' colors (for moving a file) fileAreaNumItemsHighlight The number of directories/files for - 'highlight' colors (for moving a file) \ No newline at end of file + 'highlight' colors (for moving a file) + + +5. Strings used from text.dat +============================= +Digital Distortion File Lister uses the following strings from text.dat (in +Synchronet's ctrl directory): + +- DirLibOrAll (622) +- FileSpecStarDotStar (199) +- SearchStringPrompt (76) diff --git a/xtrn/ddfilelister/revision_history.txt b/xtrn/ddfilelister/revision_history.txt index 30f34877ea..c8bd7b15df 100644 --- a/xtrn/ddfilelister/revision_history.txt +++ b/xtrn/ddfilelister/revision_history.txt @@ -5,6 +5,12 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +2.02 2022-02-13 Added the ability to do a file search (via filespec, + description, or new files since last scan). A command- + line parameter, -MODE, specifies which search to perform + (search_filename, search_description, or new_file_search + for searching; list_curdir lists files in the user's + current directory, which is the default). 2.01 2022-02-07 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 -- GitLab