UNPKG

@cumulus/ingest

Version:
91 lines (86 loc) 3.67 kB
import path from 'path'; import * as log from '@cumulus/common/log'; export interface RecursionFile { type: number | string, name: string } /** * Recur on directory, list all files, and recur into any further directories, * as specified regex segments allow. * * @param {Function} fn - list function * @param {string} currentPath - current path to list * @param {Array<string>} segments - path segments * @param {number} position - current position in the segment list * @returns {Array<Object>} - filtered contents of directory */ async function recurOnDirectory<T extends RecursionFile>( fn: (path: string) => Promise<T[]>, currentPath: string, segments: string[], position: number ): Promise<T[]> { // interpret the next path segment as a regex for filtering, and // recursively list everything when we've run out of segments const filterExpr = segments[position + 1] || '.*'; const filterRegex = new RegExp(filterExpr); const contents = await fn(currentPath); let files: T[] = []; for (let ctr = 0; ctr < contents.length; ctr += 1) { const item = contents[ctr]; // check if item passes filter if (filterRegex.test(item.name)) { if (['-', 0].includes(item.type)) { files.push(item); } else if (['d', 1].includes(item.type)) { const nextDir = path.normalize(`${currentPath}/${item.name}`); files = files.concat( // eslint-disable-next-line no-await-in-loop await recurOnDirectory(fn, nextDir, segments, position + 1) ); } } } return files; } /** * Handles recursion of a FTP/SFTP list operation * It requests a promisified list function that returns contents of * a directory on a server, filtering on provided regex segments. * * Note that calls to the list function will use either a relative or absolute path, corresponding * to the `configuredPath` passed into this function. The list function will initially be called * with '.' for a relative path or '/' for an absolute path. List functions will need to be able to * normalize or correct these paths as appropriate for their protocol. * * Further calls to the list functions will append the current path to that starting path, such * that all calls will start with either '.' or '/', regardless of additional characters, e.g. * `fn('./path')` vs. `fn('path')`. * * In the case of failure during the recursive list, this function will only apply `path.normalize` * to the `configuredPath` and then call the list function with the entire normalizedPath. * * @param {function} fn - the promisified function for listing a directory * @param {string} configuredPath - path string configured by operator, which may contain * regexes for filtering * @returns {Promise} the promise of an object that has the path is the key and * list of files as values */ export async function recursion<T extends RecursionFile>( fn: (path: string) => Promise<T[]>, configuredPath: string ): Promise<T[]> { const normalizedPath = path.normalize(configuredPath); const isAbsolutePath = path.isAbsolute(normalizedPath); try { const segments = normalizedPath .split('/') // split on divider .filter((segment) => segment.trim() !== ''); // filter out empty strings from split const startingPath = isAbsolutePath ? '/' : '.'; return await recurOnDirectory<T>(fn, startingPath, segments, -1); } catch (error) { log.error(`Encountered error during recursive list filtering: ${error}`); log.info('Falling back to unfiltered directory listing...'); return recurOnDirectory(fn, normalizedPath, [], 0); } }