igir
Version:
🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.
80 lines (79 loc) • 3.58 kB
JavaScript
import path from 'node:path';
import async from 'async';
import { ProgressBarSymbol } from '../console/progressBar.js';
import Defaults from '../globals/defaults.js';
import FsPoly, { WalkMode } from '../polyfill/fsPoly.js';
import { MoveDeleteDirs } from '../types/options.js';
import Module from './module.js';
/**
* After all output {@link File}s have been written, delete any empty subdirectories that had
* {@link File}s moved out of them. This needs to happen after all writing has finished to guarantee
* that we're done reading input {@link File}s from disk.
*/
export default class InputSubdirectoriesDeleter extends Module {
options;
constructor(options, progressBar) {
super(progressBar, InputSubdirectoriesDeleter.name);
this.options = options;
}
/**
* Delete empty input subdirectories that had {@link File}s moved out of them.
*/
async delete(movedRoms) {
if (!this.options.shouldMove()) {
// We shouldn't do anything
return [];
}
if (this.options.getMoveDeleteDirs() === MoveDeleteDirs.NEVER) {
// We shouldn't do anything
return [];
}
if (movedRoms.length === 0 && this.options.getMoveDeleteDirs() !== MoveDeleteDirs.ALWAYS) {
// We shouldn't do anything
return [];
}
this.progressBar.logTrace('deleting empty input subdirectories');
this.progressBar.setSymbol(ProgressBarSymbol.DELETING);
this.progressBar.resetProgress(0);
let dirsToMaybeDelete;
if (this.options.getMoveDeleteDirs() === MoveDeleteDirs.ALWAYS) {
// Consider every subdirectory in the input directories
dirsToMaybeDelete = new Set(await this.options.scanInputSubdirectories());
}
else {
// Only consider subdirectories that had files moved out of them
dirsToMaybeDelete = new Set(movedRoms.map((movedRom) => path.dirname(movedRom.getFilePath())));
}
const inputPathsNormalized = new Set(this.options.getInputPaths().map((inputPath) => path.normalize(inputPath)));
const deletedDirs = await this.walkAndDelete([...dirsToMaybeDelete], [...inputPathsNormalized]);
this.progressBar.logTrace('done deleting empty input subdirectories');
return deletedDirs;
}
async walkAndDelete(dirPaths, inputPathsNormalized) {
const deletedDirs = await async.filterLimit(dirPaths, Defaults.MAX_FS_THREADS, async (dirPath) => {
try {
if ((await FsPoly.walk(dirPath, WalkMode.FILES)).length === 0) {
this.progressBar.incrementTotal(1);
this.progressBar.incrementInProgress(1);
await FsPoly.rm(dirPath, { recursive: true, force: true });
return true;
}
}
catch {
/* ignored */
}
return false;
});
const parentDirs = new Set(deletedDirs
.map((emptyDir) => path.dirname(emptyDir))
.filter((parentDir) => {
const parentDirNormalized = path.normalize(parentDir);
return inputPathsNormalized.some((inputPath) => parentDirNormalized.startsWith(inputPath) && parentDirNormalized !== inputPath);
}));
if (parentDirs.size === 0) {
return deletedDirs;
}
const deletedParentDirs = await this.walkAndDelete([...parentDirs], inputPathsNormalized);
return [...deletedDirs, ...deletedParentDirs];
}
}