Skip to content

Commit c86b1b8

Browse files
committed
Updated delete file folders
1 parent 9acce28 commit c86b1b8

File tree

2 files changed

+728
-190
lines changed

2 files changed

+728
-190
lines changed

src/main/java/com/automationanywhere/botcommand/actions/device/DeleteFilesFolders.java

Lines changed: 229 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
import java.nio.file.attribute.BasicFileAttributes;
1313
import java.time.Instant;
1414
import java.time.temporal.ChronoUnit;
15-
import java.util.HashSet;
16-
import java.util.Set;
15+
import java.util.*;
1716
import java.util.regex.Pattern;
17+
import java.util.logging.Logger;
1818

1919
@BotCommand
2020
@CommandPkg(
@@ -37,6 +37,7 @@ public class DeleteFilesFolders {
3737
private static final String PROCESS_ALL_TYPES = "ALL";
3838
private static final String ERROR_THROW = "THROW";
3939
private static final String ERROR_IGNORE = "IGNORE";
40+
private static final Logger LOGGER = Logger.getLogger(DeleteFilesFolders.class.getName());
4041

4142
@Execute
4243
public void action(
@@ -130,67 +131,51 @@ public void action(
130131
) {
131132
try {
132133
Path basePath = Paths.get(inputFolderPath);
133-
Instant deletionThresholdInstant = calculateAgeThreshold(thresholdNumber.longValue(), thresholdUnit);
134-
Set<Path> directoriesToSkipDeletion = new HashSet<>();
135-
Set<Path> filesToSkipDeletion = new HashSet<>();
136-
Set<Path> directoriesToDelete = new HashSet<>();
137-
Set<Path> filesToDelete = new HashSet<>();
138-
139-
directoriesToSkipDeletion.add(basePath);
140-
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
141-
@Override
142-
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
143-
if (!recursive) {
144-
return FileVisitResult.SKIP_SUBTREE;
145-
}
146-
147-
if (skipFolders && shouldSkipDir(dir, skipFolderPathPattern)) {
148-
directoriesToSkipDeletion.add(basePath);
149-
return FileVisitResult.SKIP_SUBTREE;
150-
}
151-
if (meetsDeletionCriteria(attrs, thresholdCriteria, deletionThresholdInstant)) {
152-
directoriesToDelete.add(dir);
153-
}
154-
return FileVisitResult.CONTINUE;
155-
}
134+
if (!Files.exists(basePath)) {
135+
throw new BotCommandException("Base folder path does not exist: " + inputFolderPath);
136+
}
156137

157-
@Override
158-
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
159-
if (skipFiles && shouldSkipFile(file, skipFilePathPattern)) {
160-
filesToSkipDeletion.add(file);
161-
return FileVisitResult.CONTINUE;
162-
}
163-
if (meetsDeletionCriteria(attrs, thresholdCriteria, deletionThresholdInstant)) {
164-
filesToDelete.add(file);
165-
}
166-
return FileVisitResult.CONTINUE;
167-
}
138+
LOGGER.info("Starting deletion process for base path: " + basePath);
168139

169-
});
170-
//add all ancestors to skip to ensure they are not deleted
171-
for (Path fileToSkip : filesToSkipDeletion) {
172-
Path parent = fileToSkip.getParent();
173-
while (parent != null && !parent.equals(basePath)) {
174-
directoriesToSkipDeletion.add(parent);
175-
parent = parent.getParent();
176-
}
140+
// Phase 1: Collect data - files/directories to delete and skip
141+
FileCollector collector = new FileCollector(
142+
basePath, recursive, thresholdCriteria,
143+
calculateAgeThreshold(thresholdNumber.longValue(), thresholdUnit),
144+
skipFiles, skipFilePathPattern,
145+
skipFolders, skipFolderPathPattern
146+
);
147+
Files.walkFileTree(basePath, collector);
148+
149+
// Phase 2: Process the data - resolve conflicts between delete and skip lists
150+
DeletionProcessor processor = new DeletionProcessor(
151+
basePath,
152+
collector.getFilesToDelete(),
153+
collector.getDirectoriesToDelete(),
154+
collector.getFilesToSkip(),
155+
collector.getDirectoriesToSkip()
156+
);
157+
158+
// Phase 3: Execute deletions
159+
if (selectMethod.equalsIgnoreCase(PROCESS_ONLY_FILE_TYPE) ||
160+
selectMethod.equalsIgnoreCase(PROCESS_ALL_TYPES)) {
161+
delete(processor.getFilesToDelete(), unableToDeleteBehavior);
177162
}
178-
directoriesToDelete.removeAll(directoriesToSkipDeletion);
163+
179164
if (selectMethod.equalsIgnoreCase(PROCESS_ALL_TYPES)) {
180-
delete(filesToDelete, unableToDeleteBehavior);
181-
delete(directoriesToDelete, unableToDeleteBehavior);
182-
} else if (selectMethod.equalsIgnoreCase(PROCESS_ONLY_FILE_TYPE)) {
183-
delete(filesToDelete, unableToDeleteBehavior);
165+
delete(processor.getSortedDirectoriesToDelete(), unableToDeleteBehavior);
184166
}
185167

168+
LOGGER.info("Deletion process completed successfully");
169+
186170
} catch (IOException e) {
171+
LOGGER.severe("IO error occurred: " + e.getMessage());
187172
if (unableToDeleteBehavior.equalsIgnoreCase(ERROR_THROW)) {
188-
throw new BotCommandException(e.getMessage());
173+
throw new BotCommandException("IO error: " + e.getMessage(), e);
189174
}
190175
} catch (Exception e) {
191-
throw new BotCommandException(e.getMessage());
176+
LOGGER.severe("Unexpected error: " + e.getMessage());
177+
throw new BotCommandException("Error: " + e.getMessage(), e);
192178
}
193-
194179
}
195180

196181
private Instant calculateAgeThreshold(long threshold, String unit) {
@@ -209,41 +194,206 @@ private Instant calculateAgeThreshold(long threshold, String unit) {
209194
}
210195
}
211196

212-
private boolean shouldSkipDir(Path path, String folderPattern) {
213-
return Files.isDirectory(path) && Pattern.matches(folderPattern, path.toString());
197+
private void delete(List<Path> pathsToDelete, String unableToDeleteBehavior) {
198+
for (Path path : pathsToDelete) {
199+
try {
200+
LOGGER.info("Deleting: " + path);
201+
FileUtils.forceDelete(path.toFile());
202+
} catch (IOException e) {
203+
LOGGER.warning("Failed to delete " + path + ": " + e.getMessage());
204+
if (unableToDeleteBehavior.equalsIgnoreCase(ERROR_THROW)) {
205+
throw new BotCommandException("Failed to delete " + path + ": " + e.getMessage(), e);
206+
}
207+
}
208+
}
214209
}
215210

216-
private boolean meetsDeletionCriteria(BasicFileAttributes attrs, String thresholdCriteria, Instant ageThreshold) {
217-
Instant fileTime;
218-
switch (thresholdCriteria) {
219-
case THRESHOLD_CRITERIA_CREATION:
220-
fileTime = attrs.creationTime().toInstant();
221-
break;
222-
case THRESHOLD_CRITERIA_MODIFICATION:
223-
fileTime = attrs.lastModifiedTime().toInstant();
224-
break;
225-
default:
226-
throw new IllegalArgumentException("Unsupported threshold criteria: " + thresholdCriteria);
211+
/**
212+
* Helper class that collects files and directories during file tree traversal.
213+
*/
214+
private class FileCollector extends SimpleFileVisitor<Path> {
215+
private final Path basePath;
216+
private final boolean recursive;
217+
private final String thresholdCriteria;
218+
private final Instant deletionThresholdInstant;
219+
private final boolean skipFiles;
220+
private final String skipFilePathPattern;
221+
private final boolean skipFolders;
222+
private final String skipFolderPathPattern;
223+
224+
private final Set<Path> filesToDelete = new HashSet<>();
225+
private final Set<Path> directoriesToDelete = new HashSet<>();
226+
private final Set<Path> filesToSkip = new HashSet<>();
227+
private final Set<Path> directoriesToSkip = new HashSet<>();
228+
229+
public FileCollector(
230+
Path basePath,
231+
boolean recursive,
232+
String thresholdCriteria,
233+
Instant deletionThresholdInstant,
234+
boolean skipFiles,
235+
String skipFilePathPattern,
236+
boolean skipFolders,
237+
String skipFolderPathPattern) {
238+
this.basePath = basePath;
239+
this.recursive = recursive;
240+
this.thresholdCriteria = thresholdCriteria;
241+
this.deletionThresholdInstant = deletionThresholdInstant;
242+
this.skipFiles = skipFiles;
243+
this.skipFilePathPattern = skipFilePathPattern;
244+
this.skipFolders = skipFolders;
245+
this.skipFolderPathPattern = skipFolderPathPattern;
227246
}
228247

229-
return fileTime.isBefore(ageThreshold);//older than threshold deletion date
230-
}
248+
@Override
249+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
250+
// Skip processing of the base path for deletion (never delete the base path)
251+
if (dir.equals(basePath)) {
252+
return FileVisitResult.CONTINUE;
253+
}
231254

232-
private boolean shouldSkipFile(Path path, String filePattern) {
233-
return Files.isRegularFile(path) && Pattern.matches(filePattern, path.toFile().getAbsolutePath());
234-
}
255+
// Check if this directory should be skipped based on pattern
256+
if (skipFolders && matchesPattern(dir.toFile().getAbsolutePath(), skipFolderPathPattern)) {
257+
LOGGER.info("Skipping directory based on pattern: " + dir);
258+
directoriesToSkip.add(dir);
259+
return FileVisitResult.SKIP_SUBTREE; // Don't process contents of skipped directories
260+
}
235261

236-
private static void delete(Set<Path> filesToDelete,
237-
String unableToDeleteBehavior) {
238-
for (Path filePath : filesToDelete) {
239-
try {
240-
FileUtils.forceDelete(filePath.toFile());
241-
} catch (IOException e) {
242-
if (unableToDeleteBehavior.equalsIgnoreCase(ERROR_THROW)) {
243-
throw new BotCommandException(e.getMessage());
244-
}
262+
// Check if this directory meets the age criteria for deletion
263+
if (meetsDeletionCriteria(attrs)) {
264+
LOGGER.info("Marking directory for potential deletion: " + dir);
265+
directoriesToDelete.add(dir);
266+
}
267+
268+
// If not recursive, skip subdirectories (unless it's the base path)
269+
if (!recursive && !dir.equals(basePath)) {
270+
return FileVisitResult.SKIP_SUBTREE;
271+
}
272+
273+
return FileVisitResult.CONTINUE;
274+
}
275+
276+
@Override
277+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
278+
// Check if this file should be skipped based on pattern
279+
if (skipFiles && matchesPattern(file.toFile().getAbsolutePath(), skipFilePathPattern)) {
280+
LOGGER.info("Skipping file based on pattern: " + file);
281+
filesToSkip.add(file);
282+
return FileVisitResult.CONTINUE;
283+
}
284+
285+
// Check if this file meets the age criteria for deletion
286+
if (meetsDeletionCriteria(attrs)) {
287+
LOGGER.info("Marking file for deletion: " + file);
288+
filesToDelete.add(file);
245289
}
290+
291+
return FileVisitResult.CONTINUE;
292+
}
293+
294+
@Override
295+
public FileVisitResult visitFileFailed(Path file, IOException exc) {
296+
LOGGER.warning("Failed to visit file: " + file + " - " + exc.getMessage());
297+
return FileVisitResult.CONTINUE;
298+
}
299+
300+
private boolean meetsDeletionCriteria(BasicFileAttributes attrs) {
301+
Instant fileTime;
302+
switch (thresholdCriteria) {
303+
case THRESHOLD_CRITERIA_CREATION:
304+
fileTime = attrs.creationTime().toInstant();
305+
break;
306+
case THRESHOLD_CRITERIA_MODIFICATION:
307+
fileTime = attrs.lastModifiedTime().toInstant();
308+
break;
309+
default:
310+
throw new IllegalArgumentException("Unsupported threshold criteria: " + thresholdCriteria);
311+
}
312+
313+
return fileTime.isBefore(deletionThresholdInstant); // older than threshold
314+
}
315+
316+
private boolean matchesPattern(String pathString, String pattern) {
317+
return Pattern.matches(pattern, pathString);
318+
}
319+
320+
public Set<Path> getFilesToDelete() {
321+
return filesToDelete;
322+
}
323+
324+
public Set<Path> getDirectoriesToDelete() {
325+
return directoriesToDelete;
326+
}
327+
328+
public Set<Path> getFilesToSkip() {
329+
return filesToSkip;
330+
}
331+
332+
public Set<Path> getDirectoriesToSkip() {
333+
return directoriesToSkip;
246334
}
247335
}
248336

337+
/**
338+
* Helper class that processes file and directory lists to resolve conflicts
339+
* and prepare for deletion.
340+
*/
341+
private class DeletionProcessor {
342+
private final Path basePath;
343+
private final Set<Path> filesToDelete;
344+
private final Set<Path> directoriesToDelete;
345+
private final Set<Path> directoriesToPreserve = new HashSet<>();
346+
347+
public DeletionProcessor(
348+
Path basePath,
349+
Set<Path> filesToDelete,
350+
Set<Path> directoriesToDelete,
351+
Set<Path> filesToSkip,
352+
Set<Path> directoriesToSkip) {
353+
this.basePath = basePath;
354+
this.filesToDelete = new HashSet<>(filesToDelete);
355+
this.directoriesToDelete = new HashSet<>(directoriesToDelete);
356+
357+
// Always preserve the base path
358+
directoriesToPreserve.add(basePath);
359+
360+
// Process skipped files - preserve their parent directories
361+
for (Path skippedFile : filesToSkip) {
362+
addParentsToPreserveList(skippedFile);
363+
}
364+
365+
// Process skipped directories - preserve them and their parent directories
366+
for (Path skippedDir : directoriesToSkip) {
367+
directoriesToPreserve.add(skippedDir);
368+
addParentsToPreserveList(skippedDir);
369+
}
370+
371+
// Remove preserved directories from deletion list
372+
this.directoriesToDelete.removeAll(directoriesToPreserve);
373+
374+
LOGGER.info("Files to delete after processing: " + this.filesToDelete.size());
375+
LOGGER.info("Directories to delete after processing: " + this.directoriesToDelete.size());
376+
LOGGER.info("Directories preserved for containing skipped items: " +
377+
(directoriesToPreserve.size() - 1)); // -1 for basePath
378+
}
379+
380+
private void addParentsToPreserveList(Path path) {
381+
Path parent = path.getParent();
382+
while (parent != null && !parent.equals(basePath)) {
383+
directoriesToPreserve.add(parent);
384+
parent = parent.getParent();
385+
}
386+
}
387+
388+
public List<Path> getFilesToDelete() {
389+
return new ArrayList<>(filesToDelete);
390+
}
391+
392+
public List<Path> getSortedDirectoriesToDelete() {
393+
List<Path> sortedDirectories = new ArrayList<>(directoriesToDelete);
394+
// Sort by depth (descending) - delete deepest directories first
395+
sortedDirectories.sort((p1, p2) -> Integer.compare(p2.getNameCount(), p1.getNameCount()));
396+
return sortedDirectories;
397+
}
398+
}
249399
}

0 commit comments

Comments
 (0)