Skip to content

Commit e75f0d2

Browse files
committed
Refactor source column formatting to GitHub-style, add log rotation features, and improve file operations
- Implement GitHub-style format for source column: WORKSPACE:PATH@vVERSION#LLINE - Add EntryCountBasedTriggeringPolicy for log rotation based on entry count - Add EntryCountWithExistingFileTest to validate log entry counting and rollover - Add RuntimeContextExplorer diagnostic action for runtime context exploration - Enhance DeleteFilesFolders with better clarity and options - Update .gitignore to exclude settings.local.json - Preserve directories containing young files during deletion - Update dependencies and version to 3.5.1
1 parent 8aad4a1 commit e75f0d2

File tree

13 files changed

+1345
-72
lines changed

13 files changed

+1345
-72
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/.gradle/
44
/.idea/
55
/src/test/target/
6+
/.claude/settings.local.json

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Designed to streamline bot development, enhance logging, and facilitate comprehe
1313
## Key Features
1414

1515
### [Robust Logging](https://github.com/A360-Tools/Bot-Framework/blob/main/docs/logs/LogMessage.md)
16+
![Animation](https://github.com/A360-Tools/Bot-Framework/assets/82057278/6f8c9268-d411-4b62-93c2-74cca016e13e)
1617
- Global session logger for comprehensive logging across subtasks.
1718
- Automatic capture of source task details, environment details for enhanced traceability.
1819
- Visualization of complex/nested variables for efficient debugging and tracking.

build.gradle

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ configure(allprojects){
2222

2323
ext {
2424
groupName = 'com.automationanywhere'
25-
testNgVersion = '7.7.0'
26-
loggerVersion = '2.24.3'
25+
testNgVersion = '7.11.0'
26+
loggerVersion = '2.25.2'
2727
jnaVersion = '5.3.1'
28-
version '3.2.4'
28+
version '3.5.1'
2929
}
3030
group "$groupName"
3131

@@ -71,23 +71,23 @@ configure(allprojects){
7171
testImplementation name: 'package-compileonly-sdk', version: '1.5.0'
7272
testImplementation group: "org.testng", name:"testng", version: "$testNgVersion"
7373

74-
testImplementation 'com.google.protobuf:protobuf-java:4.30.2' //table,record type use this internally
74+
testImplementation 'com.google.protobuf:protobuf-java:4.32.1' //table,record type use this internally
7575

7676
// https://mvnrepository.com/artifact/org.json/json
77-
implementation 'org.json:json:20250107'
77+
implementation 'org.json:json:20250517'
7878

7979
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api
8080
implementation group: "org.apache.logging.log4j", name: "log4j-api", version: "$loggerVersion"
8181
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
8282
implementation group: "org.apache.logging.log4j", name: "log4j-core", version: "$loggerVersion"
8383

8484
// https://mvnrepository.com/artifact/commons-io/commons-io
85-
implementation 'commons-io:commons-io:2.19.0'
85+
implementation 'commons-io:commons-io:2.20.0'
8686
// https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml
8787
implementation 'org.apache.poi:poi-ooxml:5.4.1'
8888

8989
// https://mvnrepository.com/artifact/org.apache.commons/commons-csv
90-
implementation 'org.apache.commons:commons-csv:1.14.0'
90+
implementation 'org.apache.commons:commons-csv:1.14.1'
9191
}
9292

9393
}

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

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
import java.time.temporal.ChronoUnit;
1515
import java.util.*;
1616
import java.util.regex.Pattern;
17+
import java.util.regex.PatternSyntaxException;
1718
import java.util.logging.Logger;
1819

1920
@BotCommand
2021
@CommandPkg(
2122
label = "Clean Directory",
22-
node_label = "{{inputFolderPath}} by deleting {{selectMethod}} {{recursive}}",
23-
description = "Remove files/folders based on rule set",
23+
node_label = "Clean {{inputFolderPath}} by deleting {{selectMethod}} older than {{thresholdNumber}} {{thresholdUnit}}",
24+
description = "Delete old files and folders based on age threshold. Useful for cleaning logs, temp files, and backups.",
2425
icon = "delete_folders.svg",
2526
name = "device_delete_files_folders",
2627
group_label = "Device",
@@ -42,90 +43,93 @@ public class DeleteFilesFolders {
4243
@Execute
4344
public void action(
4445
@Idx(index = "1", type = AttributeType.FILE)
45-
@Pkg(label = "Enter base folder path", description = "Files/Folders will be scanned within this folder " +
46-
"for deletion")
46+
@Pkg(label = "Target folder to clean", description = "Base folder path where cleanup will be performed. " +
47+
"This folder itself will never be deleted, only its contents.")
4748
@NotEmpty
4849
@FileFolder
4950
String inputFolderPath,
5051

5152
@Idx(index = "2", type = AttributeType.SELECT, options = {
52-
@Idx.Option(index = "2.1", pkg = @Pkg(label = "Directories and Files", value = PROCESS_ALL_TYPES,
53-
node_label = "it's directories and files")),
54-
@Idx.Option(index = "2.2", pkg = @Pkg(label = "Files Only", value = PROCESS_ONLY_FILE_TYPE,
55-
node_label = "it's files"))})
56-
@Pkg(label = "Deletion option", default_value = PROCESS_ALL_TYPES,
53+
@Idx.Option(index = "2.1", pkg = @Pkg(label = "Both files and folders", value = PROCESS_ALL_TYPES,
54+
node_label = "files and folders")),
55+
@Idx.Option(index = "2.2", pkg = @Pkg(label = "Files only (keep folder structure)", value = PROCESS_ONLY_FILE_TYPE,
56+
node_label = "files only"))})
57+
@Pkg(label = "What to delete", default_value = PROCESS_ALL_TYPES,
5758
default_value_type = DataType.STRING)
5859
@NotEmpty
5960
@SelectModes
6061
String selectMethod,
6162

6263
@Idx(index = "3", type = AttributeType.CHECKBOX)
63-
@Pkg(label = "All subdirectories are searched as well", default_value = "true",
64-
node_label = "Action all subdirectories",
64+
@Pkg(label = "Include all subfolders", default_value = "true",
65+
node_label = "including subfolders",
66+
description = "When checked: processes all nested folders. When unchecked: only processes immediate folder contents.",
6567
default_value_type = DataType.BOOLEAN)
6668
Boolean recursive,
6769

6870
@Idx(index = "4", type = AttributeType.NUMBER)
69-
@Pkg(label = "Threshold number", default_value_type = DataType.NUMBER, default_value = "30",
70-
description = "any file/folder with threshold age value older than this value will be " +
71-
"deleted")
71+
@Pkg(label = "Delete items older than", default_value_type = DataType.NUMBER, default_value = "30",
72+
description = "Age threshold for deletion. Example: 7 (with Days selected) = delete items 7+ days old. " +
73+
"Use 0 to delete all items regardless of age.")
7274
@NotEmpty
7375
@GreaterThanEqualTo("0")
7476
@NumberInteger
7577
Number thresholdNumber,
7678

7779
@Idx(index = "5", type = AttributeType.SELECT, options = {
78-
@Idx.Option(index = "5.1", pkg = @Pkg(label = "DAY", value = THRESHOLD_UNIT_DAY)),
79-
@Idx.Option(index = "5.2", pkg = @Pkg(label = "HOUR", value = THRESHOLD_UNIT_HOUR)),
80-
@Idx.Option(index = "5.3", pkg = @Pkg(label = "MINUTE", value = THRESHOLD_UNIT_MINUTE)),
81-
@Idx.Option(index = "5.4", pkg = @Pkg(label = "SECOND", value = THRESHOLD_UNIT_SECOND))})
82-
@Pkg(label = "Threshold Unit", default_value = THRESHOLD_UNIT_DAY,
80+
@Idx.Option(index = "5.1", pkg = @Pkg(label = "Days", value = THRESHOLD_UNIT_DAY)),
81+
@Idx.Option(index = "5.2", pkg = @Pkg(label = "Hours", value = THRESHOLD_UNIT_HOUR)),
82+
@Idx.Option(index = "5.3", pkg = @Pkg(label = "Minutes", value = THRESHOLD_UNIT_MINUTE)),
83+
@Idx.Option(index = "5.4", pkg = @Pkg(label = "Seconds", value = THRESHOLD_UNIT_SECOND))})
84+
@Pkg(label = "Time unit", default_value = THRESHOLD_UNIT_DAY,
8385
default_value_type = DataType.STRING)
8486
@NotEmpty
8587
@SelectModes
8688
String thresholdUnit,
8789

8890
@Idx(index = "6", type = AttributeType.SELECT, options = {
89-
@Idx.Option(index = "6.1", pkg = @Pkg(label = "CREATION", value = THRESHOLD_CRITERIA_CREATION)),
90-
@Idx.Option(index = "6.2", pkg = @Pkg(label = "LAST MODIFICATION", value =
91+
@Idx.Option(index = "6.1", pkg = @Pkg(label = "Creation date (when file was created)", value = THRESHOLD_CRITERIA_CREATION)),
92+
@Idx.Option(index = "6.2", pkg = @Pkg(label = "Last modified date (when file was last changed)", value =
9193
THRESHOLD_CRITERIA_MODIFICATION))})
92-
@Pkg(label = "Threshold age type", default_value = THRESHOLD_CRITERIA_CREATION,
93-
default_value_type = DataType.STRING)
94+
@Pkg(label = "Age based on", default_value = THRESHOLD_CRITERIA_MODIFICATION,
95+
default_value_type = DataType.STRING,
96+
description = "For logs, use 'Last modified' as they are continuously updated.")
9497
@NotEmpty
9598
@SelectModes
9699
String thresholdCriteria,
97100

98101
@Idx(index = "7", type = AttributeType.CHECKBOX)
99-
@Pkg(label = "Ignore specific folder paths", default_value = "false", default_value_type =
102+
@Pkg(label = "Skip specific folders (preserve them)", default_value = "false", default_value_type =
100103
DataType.BOOLEAN)
101104
Boolean skipFolders,
102105

103106
@Idx(index = "7.1", type = AttributeType.TEXT)
104-
@Pkg(label = "Regex pattern to match folder paths to ignore",
105-
description = ".*\\\\subDirectory" + " to skip folder called subDirectory on windows platform" +
106-
"Matching will be done on absolute path in OS file separator format.")
107+
@Pkg(label = "Folder pattern to skip (regex)",
108+
description = "Examples: '.*\\\\backup$' skips folders named 'backup', '.*\\\\(archive|important).*' skips folders containing 'archive' or 'important'. " +
109+
"Pattern matches against full absolute path.")
107110
@NotEmpty
108111
String skipFolderPathPattern,
109112

110113
@Idx(index = "8", type = AttributeType.CHECKBOX)
111-
@Pkg(label = "Ignore specific file paths", default_value = "false", default_value_type =
114+
@Pkg(label = "Skip specific files (preserve them)", default_value = "false", default_value_type =
112115
DataType.BOOLEAN)
113116
Boolean skipFiles,
114117

115118
@Idx(index = "8.1", type = AttributeType.TEXT)
116-
@Pkg(label = "Regex pattern to match file paths to ignore", description =
117-
".*\\.txt$" + " to skip all text files on windows platform. Matching will be done on absolute " +
118-
"path in OS file separator format.")
119+
@Pkg(label = "File pattern to skip (regex)", description =
120+
"Examples: '.*\\.log$' skips .log files, '.*\\.(txt|csv)$' skips .txt and .csv files, '.*important.*' skips files with 'important' in name. " +
121+
"Pattern matches against full absolute path.")
119122
@NotEmpty
120123
String skipFilePathPattern,
121124

122125
@Idx(index = "9", type = AttributeType.RADIO, options = {
123-
@Idx.Option(index = "9.1", pkg = @Pkg(label = "Throw error", value = ERROR_THROW)),
124-
@Idx.Option(index = "9.2", pkg = @Pkg(label = "Ignore", value = ERROR_IGNORE))
126+
@Idx.Option(index = "9.1", pkg = @Pkg(label = "Stop and throw error (fail the bot)", value = ERROR_THROW)),
127+
@Idx.Option(index = "9.2", pkg = @Pkg(label = "Continue silently (skip locked files)", value = ERROR_IGNORE))
125128
})
126-
@Pkg(label = "If certain files/folders cannot be deleted", default_value_type = DataType.STRING,
129+
@Pkg(label = "When files cannot be deleted (locked/in-use)", default_value_type = DataType.STRING,
127130
description =
128-
"Behavior in case a file is locked/missing permission", default_value = ERROR_IGNORE)
131+
"Choose 'Continue silently' for log cleanup where some files may be actively written. Choose 'Stop and throw error' when all files must be deleted.",
132+
default_value = ERROR_IGNORE)
129133
@NotEmpty
130134
String unableToDeleteBehavior
131135
) {
@@ -152,7 +156,8 @@ public void action(
152156
collector.getFilesToDelete(),
153157
collector.getDirectoriesToDelete(),
154158
collector.getFilesToSkip(),
155-
collector.getDirectoriesToSkip()
159+
collector.getDirectoriesToSkip(),
160+
collector.getYoungFiles()
156161
);
157162

158163
// Phase 3: Execute deletions
@@ -225,6 +230,7 @@ private static class FileCollector extends SimpleFileVisitor<Path> {
225230
private final Set<Path> directoriesToDelete = new HashSet<>();
226231
private final Set<Path> filesToSkip = new HashSet<>();
227232
private final Set<Path> directoriesToSkip = new HashSet<>();
233+
private final Set<Path> youngFiles = new HashSet<>(); // Track files too young to delete
228234

229235
public FileCollector(
230236
Path basePath,
@@ -287,6 +293,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
287293
if (meetsDeletionCriteria(attrs)) {
288294
LOGGER.info("Marking file for deletion: " + file);
289295
filesToDelete.add(file);
296+
} else {
297+
// Track files that are too young to delete
298+
youngFiles.add(file);
290299
}
291300

292301
return FileVisitResult.CONTINUE;
@@ -311,11 +320,17 @@ private boolean meetsDeletionCriteria(BasicFileAttributes attrs) {
311320
throw new IllegalArgumentException("Unsupported threshold criteria: " + thresholdCriteria);
312321
}
313322

314-
return fileTime.isBefore(deletionThresholdInstant); // older than threshold
323+
// Files at or before the threshold instant should be deleted
324+
// Using !isAfter instead of isBefore to include files exactly at the threshold
325+
return !fileTime.isAfter(deletionThresholdInstant);
315326
}
316327

317328
private boolean matchesPattern(String pathString, String pattern) {
318-
return Pattern.matches(pattern, pathString);
329+
try {
330+
return Pattern.matches(pattern, pathString);
331+
} catch (PatternSyntaxException e) {
332+
throw new BotCommandException("Invalid regex pattern: '" + pattern + "'. " + e.getMessage());
333+
}
319334
}
320335

321336
public Set<Path> getFilesToDelete() {
@@ -333,6 +348,10 @@ public Set<Path> getFilesToSkip() {
333348
public Set<Path> getDirectoriesToSkip() {
334349
return directoriesToSkip;
335350
}
351+
352+
public Set<Path> getYoungFiles() {
353+
return youngFiles;
354+
}
336355
}
337356

338357
/**
@@ -350,7 +369,8 @@ public DeletionProcessor(
350369
Set<Path> filesToDelete,
351370
Set<Path> directoriesToDelete,
352371
Set<Path> filesToSkip,
353-
Set<Path> directoriesToSkip) {
372+
Set<Path> directoriesToSkip,
373+
Set<Path> youngFiles) {
354374
this.basePath = basePath;
355375
this.filesToDelete = new HashSet<>(filesToDelete);
356376
this.directoriesToDelete = new HashSet<>(directoriesToDelete);
@@ -369,6 +389,11 @@ public DeletionProcessor(
369389
addParentsToPreserveList(skippedDir);
370390
}
371391

392+
// Process young files - preserve their parent directories
393+
for (Path youngFile : youngFiles) {
394+
addParentsToPreserveList(youngFile);
395+
}
396+
372397
// Remove preserved directories from deletion list
373398
this.directoriesToDelete.removeAll(directoriesToPreserve);
374399

0 commit comments

Comments
 (0)