1
1
package org .commonwl .view ;
2
2
3
3
4
+ import org .apache .commons .io .FileUtils ;
5
+ import org .apache .commons .io .file .AccumulatorPathVisitor ;
6
+ import org .apache .commons .io .filefilter .AgeFileFilter ;
4
7
import org .commonwl .view .workflow .QueuedWorkflowRepository ;
5
8
import org .slf4j .Logger ;
6
9
import org .slf4j .LoggerFactory ;
9
12
import org .springframework .scheduling .annotation .Scheduled ;
10
13
import org .springframework .stereotype .Component ;
11
14
15
+ import java .io .File ;
16
+ import java .io .IOException ;
17
+ import java .nio .file .Files ;
18
+ import java .nio .file .Path ;
19
+ import java .nio .file .Paths ;
20
+ import java .time .Duration ;
21
+ import java .time .Instant ;
22
+ import java .util .Arrays ;
12
23
import java .util .Calendar ;
24
+ import java .util .Collections ;
13
25
import java .util .Date ;
26
+ import java .util .HashSet ;
27
+ import java .util .List ;
28
+ import java .util .Set ;
29
+ import java .util .stream .Stream ;
14
30
15
31
/**
16
32
* Scheduler class for recurrent processes.
@@ -24,6 +40,16 @@ public class Scheduler {
24
40
@ Value ("${queuedWorkflowAgeLimitHours}" )
25
41
private Integer QUEUED_WORKFLOW_AGE_LIMIT_HOURS ;
26
42
43
+ @ Value ("${tmpDirAgeLimitDays}" )
44
+ private Integer TMP_DIR_AGE_LIMIT_DAYS ;
45
+
46
+ @ Value ("${bundleStorage}" )
47
+ private String bundleStorage ;
48
+ @ Value ("${graphvizStorage}" )
49
+ private String graphvizStorage ;
50
+ @ Value ("${gitStorage}" )
51
+ private String gitStorage ;
52
+
27
53
@ Autowired
28
54
public Scheduler (QueuedWorkflowRepository queuedWorkflowRepository ) {
29
55
this .queuedWorkflowRepository = queuedWorkflowRepository ;
@@ -55,4 +81,70 @@ public void removeOldQueuedWorkflowEntries() {
55
81
logger .info (queuedWorkflowRepository .deleteByTempRepresentation_RetrievedOnLessThanEqual (removeTime )
56
82
+ " Old queued workflows removed" );
57
83
}
84
+
85
+ /**
86
+ * Scheduled function to delete old temporary directories.
87
+ *
88
+ * <p>Will scan each temporary directory (graphviz, RO, git), searching
89
+ * for files exceeding a specified threshold.</p>
90
+ *
91
+ * <p>It scans the first level directories, i.e. it does not recursively
92
+ * scans directories. So it will delete any RO or Git repository directories
93
+ * that exceed the threshold. Similarly, it will delete any graph (svg, png,
94
+ * etc) that also exceed it.</p>
95
+ *
96
+ * <p>Errors logged through Logger. Settings in Spring application properties
97
+ * file.</p>
98
+ *
99
+ * @since 1.4.5
100
+ */
101
+ @ Scheduled (cron = "${cron.clearTmpDir}" )
102
+ public void clearTmpDir () {
103
+ // Temporary files used for graphviz, RO, and git may be stored in different
104
+ // locations, so we will collect all of them here.
105
+ List <String > temporaryDirectories = Stream .of (bundleStorage , graphvizStorage , gitStorage )
106
+ .distinct ()
107
+ .toList ();
108
+ temporaryDirectories .forEach (this ::clearDirectory );
109
+ }
110
+
111
+ /**
112
+ * For a given temporary directory, scans it (not recursively) for files and
113
+ * directories exceeding the age limit threshold.
114
+ *
115
+ * @since 1.4.5
116
+ * @see <a href="https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/filefilter/AgeFileFilter.html">https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/filefilter/AgeFileFilter.html</a>
117
+ * @param temporaryDirectory temporary directory
118
+ */
119
+ private void clearDirectory (String temporaryDirectory ) {
120
+ final Path dir = Paths .get (temporaryDirectory );
121
+ final Instant cutoff = Instant .now ().minus (Duration .ofDays (TMP_DIR_AGE_LIMIT_DAYS ));
122
+ // TODO: Commons IO 2.12 has a constructor that takes an Instant; drop the Date#from call here when we upgrade.
123
+ final AgeFileFilter fileAndDirFilter = new AgeFileFilter (Date .from (cutoff ));
124
+ final AccumulatorPathVisitor visitor = AccumulatorPathVisitor .withLongCounters (fileAndDirFilter , fileAndDirFilter );
125
+
126
+ // Walk the files.
127
+ try {
128
+ Files .walkFileTree (dir , Collections .emptySet (), /* maxDepth */ 1 , visitor );
129
+ } catch (IOException e ) {
130
+ // Really unexpected. walkFileTree should throw an IllegalArgumentException for negative maxDepth (clearly
131
+ // not happening here), a SecurityException if the security manager denies access, or this IOException in
132
+ // the cases where an I/O error happened (disk error, OS error, file not found, etc.). So just a warning.
133
+ logger .warn (String .format ("Unexpected I/O error was thrown walking directory [%s]: %s" , dir .toString (), e .getMessage ()), e );
134
+ }
135
+
136
+ // Delete the directories accumulated by the visitor.
137
+ final List <Path > dirList = visitor .getDirList ();
138
+ dirList .forEach (tooOldDeleteMe -> {
139
+ File fileToDelete = tooOldDeleteMe .toFile ();
140
+ try {
141
+ FileUtils .forceDelete (fileToDelete );
142
+ } catch (IOException e ) {
143
+ // Here we probably have a more serious case. Since the Git repository, RO directory, or graphs are
144
+ // not expected to be in use, and the application must have access, I/O errors are not expected and
145
+ // must be treated as errors.
146
+ logger .error (String .format ("Failed to delete old temporary file or directory [%s]: %s" , fileToDelete .getAbsolutePath (), e .getMessage ()), e );
147
+ }
148
+ });
149
+ }
58
150
}
0 commit comments