@zowe/zos-files-for-zowe-sdk
Version:
Zowe SDK to interact with files and data sets on z/OS
428 lines • 22.5 kB
JavaScript
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Search = void 0;
const imperative_1 = require("@zowe/imperative");
const list_1 = require("../list");
const get_1 = require("../get");
const core_for_zowe_sdk_1 = require("@zowe/core-for-zowe-sdk");
const lodash_1 = require("lodash");
/**
* This class holds helper functions that are used to list data sets and its members through the z/OS MF APIs
*/
class Search {
/**
* Retrieve all data sets and members to search
*
* @param {AbstractSession} session - z/OS MF connection info
* @param {ISearchOptions} searchOptions - contains the data set search options,
* including name, searchString, timeout, and maxConcurrentRequests
*
* @returns {Promise<IZosFilesResponse>} A response indicating the outcome of the API
*
* @throws {ImperativeError} data set name must be set
* @throws {Error} When the {@link ZosmfRestClient} throws an error
*/
static dataSets(session, searchOptions) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
imperative_1.ImperativeExpect.toBeDefinedAndNonBlank(searchOptions.pattern, "pattern");
imperative_1.ImperativeExpect.toBeDefinedAndNonBlank(searchOptions.searchString, "searchString");
const failedDatasets = [];
const origSearchQuery = searchOptions.searchString;
let timer;
this.timerExpired = false;
// Handle timeouts
if (searchOptions.timeout) {
timer = setTimeout(() => {
this.timerExpired = true;
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, searchOptions.timeout * 1000);
}
// Handle progress bars
if (searchOptions.progressTask) {
searchOptions.progressTask.stageName = imperative_1.TaskStage.IN_PROGRESS;
searchOptions.progressTask.percentComplete = 0;
searchOptions.progressTask.statusMessage = "Getting search list...";
}
// List all data sets that match the search term
const searchItemsQueue = [];
const partitionedDataSets = [];
// We are in trouble if list fails - exit if it does
try {
const response = yield list_1.List.dataSetsMatchingPattern(session, [searchOptions.pattern], Object.assign(Object.assign({}, searchOptions.listOptions), { attributes: true, maxConcurrentRequests: searchOptions.maxConcurrentRequests, maxLength: searchOptions.searchExactName ? 1 : undefined }));
for (const resp of response.apiResponse) {
// Skip anything that doesn't have a DSORG or is migrated
if (resp.dsorg && !(resp.migr && resp.migr.toLowerCase() === "yes")) {
// If we are looking for an exact name, ensure the name matches
if (!searchOptions.searchExactName || resp.dsname == searchOptions.pattern.toUpperCase()) {
if (resp.dsorg === "PS") { // Sequential
searchItemsQueue.push({ dsn: resp.dsname });
}
else if (resp.dsorg.startsWith("PO")) { // Partitioned
partitionedDataSets.push(resp.dsname);
}
}
}
}
}
catch (err) {
throw new imperative_1.ImperativeError({ msg: "Failed to get list of data sets to search", causeErrors: err });
}
// Get a list of members if a data set is a PDS
for (const pds of partitionedDataSets) {
try {
const response = yield list_1.List.allMembers(session, pds, searchOptions.listOptions);
if (response.apiResponse.items.length > 0) {
for (const item of response.apiResponse.items) {
if (item.member != undefined) {
searchItemsQueue.push({ dsn: pds, member: item.member });
}
}
}
}
catch (err) {
failedDatasets.push(pds);
}
}
// Call the callback if it exists.
let resp = true;
if (searchOptions.continueSearch) {
// Call with a clone of the search items queue. Extenders should not be modifying the original.
resp = yield searchOptions.continueSearch((0, lodash_1.cloneDeep)(searchItemsQueue));
}
// Return if the callback set response to false, null, undefined, or anything that is not boolean true
if (resp !== true) {
return {
success: false,
commandResponse: "The search was cancelled."
};
}
// Cast the search items queue to ISearchItem. Clone so the original search items are not being modified and type errors aren't thrown
// when/if we reference the original list again.
let searchItems = (0, lodash_1.cloneDeep)(searchItemsQueue);
// Start searching on the mainframe if applicable
if (searchOptions.mainframeSearch) {
const response = yield this.searchOnMainframe(session, searchOptions, searchItems);
searchItems = response.responses;
failedDatasets.push(...response.failures);
}
// Start searching locally
const response = yield this.searchLocal(session, searchOptions, searchItems);
const matchResponses = response.responses;
failedDatasets.push(...response.failures);
if (searchOptions.progressTask) {
if (this.timerExpired && failedDatasets.length >= 1) {
searchOptions.progressTask.stageName = imperative_1.TaskStage.FAILED;
searchOptions.progressTask.percentComplete = 100;
searchOptions.progressTask.statusMessage = "Operation timed out";
}
else if ((_a = searchOptions.abortSearch) === null || _a === void 0 ? void 0 : _a.call(searchOptions)) {
searchOptions.progressTask.stageName = imperative_1.TaskStage.FAILED;
searchOptions.progressTask.percentComplete = 100;
searchOptions.progressTask.statusMessage = "Operation cancelled";
}
else {
searchOptions.progressTask.stageName = imperative_1.TaskStage.COMPLETE;
searchOptions.progressTask.percentComplete = 100;
searchOptions.progressTask.statusMessage = "Search complete";
}
}
if (this.timerExpired) {
this.timerExpired = false;
}
if (timer) {
clearTimeout(timer);
}
// Sort responses to make it pretty
matchResponses.sort((a, b) => {
const sort = a.dsn.localeCompare(b.dsn);
if (sort === 0) {
return a.member.localeCompare(b.member);
}
else {
return sort;
}
});
const chalk = imperative_1.TextUtils.chalk;
const apiResponse = {
success: failedDatasets.length <= 0,
commandResponse: "Found \"" + chalk.yellow(origSearchQuery) + "\" in " +
chalk.yellow(matchResponses.length) + " data sets and PDS members",
apiResponse: matchResponses
};
if ((_b = searchOptions.abortSearch) === null || _b === void 0 ? void 0 : _b.call(searchOptions)) {
// Notify the user the search was cancelled, and give the results from before the cancellation.
apiResponse.commandResponse = "The search was cancelled.\n" + apiResponse.commandResponse;
}
if (matchResponses.length >= 1) {
apiResponse.commandResponse += ":\n";
for (const entry of matchResponses) {
apiResponse.commandResponse += "\n" + chalk.yellow("Data Set") + " \"" + entry.dsn + "\"";
if (entry.member) {
apiResponse.commandResponse += " | " + chalk.yellow("Member") + " \"" + entry.member + "\":\n";
}
else {
apiResponse.commandResponse += ":\n";
}
let maxLine = 0;
let maxCol = 0;
for (const { line, column } of entry.matchList) {
if (line > maxLine) {
maxLine = line;
}
if (column > maxCol) {
maxCol = column;
}
}
const lineLen = maxLine.toString().length;
const colLen = maxCol.toString().length;
for (const { line, column, contents, length } of entry.matchList) {
// eslint-disable-next-line no-control-regex
let localContents = contents.replace(/[\u0000-\u001F\u007F-\u009F]/g, "\uFFFD");
const beforeString = chalk.grey(localContents.substring(0, column - 1));
const selectedString = chalk.white.bold(localContents.substring(column - 1, column - 1 + length));
const afterString = chalk.grey(localContents.substring(column - 1 + length, localContents.length + 1));
localContents = beforeString + selectedString + afterString;
apiResponse.commandResponse += chalk.yellow("Line:") + " " + line.toString().padStart(lineLen) +
", " + chalk.yellow("Column:") + " " + column.toString().padStart(colLen) + ", " + chalk.yellow("Contents:") +
" " + localContents + "\n";
}
}
}
else {
apiResponse.commandResponse += ".";
}
if (!apiResponse.success) {
apiResponse.errorMessage = "The following data set(s) failed to be searched: \n";
for (const entry of failedDatasets) {
apiResponse.errorMessage += entry + "\n";
}
}
return apiResponse;
});
}
/**
* Perform a prelimiary search on z/OSMF
*
* @param {AbstractSession} session - z/OS MF connection info
* @param {ISearchOptions} searchOptions - Search options
* @param {ISearchItem[]} searchItems - List of items for searching
*
* @returns {Promise<string[]>} A list of members that contain the searched for term
*
* @throws {ImperativeError} when a download fails, or timeout is exceeded.
*/
static searchOnMainframe(session, searchOptions, searchItems) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const matches = [];
const failures = [];
const total = searchItems.length;
let complete = 0;
let searchAborted = (_a = searchOptions.abortSearch) === null || _a === void 0 ? void 0 : _a.call(searchOptions);
const createSearchPromise = (searchItem) => __awaiter(this, void 0, void 0, function* () {
var _a;
if (!this.timerExpired && !searchAborted) {
if ((_a = searchOptions.abortSearch) === null || _a === void 0 ? void 0 : _a.call(searchOptions)) {
searchAborted = true;
}
// Update the progress bar
if (searchOptions.progressTask) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
searchOptions.progressTask.percentComplete = Math.floor(complete / total / 2 * 100);
searchOptions.progressTask.statusMessage = "Initial mainframe search: " + complete + " of " + total + " entries checked";
}
// Handle case sensitivity
if (searchOptions.caseSensitive == undefined || searchOptions.caseSensitive === false) {
searchOptions.searchString = searchOptions.searchString.toLowerCase();
}
// Set up the query
let queryParams = "";
if (searchOptions.regex) {
queryParams = "?research=" + encodeURIComponent(searchOptions.searchString);
}
else {
queryParams = "?search=" + encodeURIComponent(searchOptions.searchString);
}
queryParams += "&maxreturnsize=1";
if (searchOptions.caseSensitive === true) {
queryParams += "&insensitive=false";
}
let dsn = searchItem.dsn;
if (searchItem.member) {
dsn += "(" + searchItem.member + ")";
}
// Get the response from the mainframe
let getResponseBuffer;
try {
getResponseBuffer = yield get_1.Get.dataSet(session, dsn, Object.assign(Object.assign({}, searchOptions.getOptions), { queryParams }));
}
catch (err) {
failures.push(dsn);
complete++;
return;
}
if (!(getResponseBuffer == null || getResponseBuffer.byteLength === 0)) {
matches.push(searchItem);
}
complete++;
}
else {
if (searchItem.member) {
failures.push(searchItem.dsn + "(" + searchItem.member + ")");
}
else {
failures.push(searchItem.dsn);
}
complete++;
}
});
yield (0, core_for_zowe_sdk_1.asyncPool)(searchOptions.maxConcurrentRequests || 1, searchItems, createSearchPromise);
return { responses: matches, failures };
});
}
/**
* Perform a deep search locally
*
* @param {AbstractSession} session - z/OS MF connection info
* @param {ISearchOptions} searchOptions - Search options
*
* @returns {Promise<IZosFilesMatchResponse[]>} A list of members that contain the searched for term, and locations where the term appears
*
* @throws {ImperativeError} when a download fails, or timeout is exceeded.
*/
static searchLocal(session, searchOptions, searchItems) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const matchedItems = [];
const failures = [];
const total = searchItems.length;
let complete = 0;
let searchAborted = (_a = searchOptions.abortSearch) === null || _a === void 0 ? void 0 : _a.call(searchOptions);
const createFindPromise = (searchItem) => __awaiter(this, void 0, void 0, function* () {
var _a;
if (!this.timerExpired && !searchAborted) {
if ((_a = searchOptions.abortSearch) === null || _a === void 0 ? void 0 : _a.call(searchOptions)) {
searchAborted = true;
}
// Handle the progress bars
if (searchOptions.progressTask) {
if (searchOptions.mainframeSearch) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
searchOptions.progressTask.percentComplete = Math.floor(complete / total / 2 * 100 + 50);
searchOptions.progressTask.statusMessage = "Performing search: " + complete + " of " + total + " entries checked";
}
else {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
searchOptions.progressTask.percentComplete = Math.floor(complete / total * 100);
searchOptions.progressTask.statusMessage = "Performing search: " + complete + " of " + total + " entries checked";
}
}
// Set up the query
let dsn = searchItem.dsn;
if (searchItem.member) {
dsn += "(" + searchItem.member + ")";
}
// Get the item
let getResponseBuffer;
try {
getResponseBuffer = yield get_1.Get.dataSet(session, dsn, searchOptions.getOptions);
}
catch (err) {
failures.push(dsn);
complete++;
return;
}
const getResponseString = getResponseBuffer.toString();
const getResponseStringArray = getResponseString.split(/\r?\n/);
// Handle case sensitivity
if (searchOptions.caseSensitive == undefined || searchOptions.caseSensitive === false) {
searchOptions.searchString = searchOptions.searchString.toLowerCase();
}
// Perform the search
const indicies = [];
let lineNum = 0;
for (const line of getResponseStringArray) {
// Handle temporary storage of comparison data - we want the original to return to the caller
let searchLine = line;
if (searchOptions.caseSensitive == undefined || searchOptions.caseSensitive === false) {
searchLine = line.toLowerCase();
}
if (searchOptions.regex) {
const regex = new RegExp(searchOptions.searchString, searchOptions.caseSensitive ? "g" : "gi");
const matches = searchLine.matchAll(regex);
for (const match of matches) {
indicies.push({
line: lineNum + 1,
column: match.index + 1,
contents: line,
length: match[0].length
});
}
}
else {
if (searchLine.includes(searchOptions.searchString)) {
let lastCol = 0;
let lastColIndexPlusLen = 0;
while (lastCol != -1) {
const column = searchLine.indexOf(searchOptions.searchString, lastColIndexPlusLen);
lastCol = column;
lastColIndexPlusLen = column + searchOptions.searchString.length;
if (column != -1) {
// Append the real line - 1 indexed
indicies.push({
line: lineNum + 1,
column: column + 1,
contents: line,
length: searchOptions.searchString.length
});
}
}
}
}
lineNum++;
}
if (indicies.length > 0) {
searchItem.matchList = indicies;
matchedItems.push(searchItem);
}
complete++;
}
else {
if (searchItem.member) {
failures.push(searchItem.dsn + "(" + searchItem.member + ")");
}
else {
failures.push(searchItem.dsn);
}
complete++;
}
});
yield (0, core_for_zowe_sdk_1.asyncPool)(searchOptions.maxConcurrentRequests || 1, searchItems, createFindPromise);
return { responses: matchedItems, failures };
});
}
}
exports.Search = Search;
/* Flag for an expired timeout */
Search.timerExpired = false;
//# sourceMappingURL=Search.js.map
;