1
1
package com .kttdevelopment .simplehttpserver .handler ;
2
2
3
- import java .io .File ;
4
- import java .io .IOException ;
3
+ import java .io .*;
5
4
import java .nio .file .*;
6
5
import java .util .*;
7
6
import java .util .concurrent .ConcurrentHashMap ;
7
+ import java .util .function .Consumer ;
8
8
9
9
import static java .nio .file .StandardWatchEventKinds .*;
10
10
14
14
* @see FileHandler
15
15
* @see FileEntry
16
16
* @since 02.00.00
17
- * @version 03.05.00
17
+ * @version 03.05.01
18
18
* @author Ktt Development
19
19
*/
20
20
class DirectoryEntry {
@@ -25,6 +25,7 @@ class DirectoryEntry {
25
25
private final boolean isWalkthrough ;
26
26
27
27
private final Map <String ,FileEntry > preloadedFiles = new ConcurrentHashMap <>(); // preload/watchload only
28
+ private final Path directoryPath ;
28
29
29
30
/**
30
31
* Create a directory entry.
@@ -33,7 +34,7 @@ class DirectoryEntry {
33
34
* @param adapter how to process the bytes in {@link #getBytes(String)}
34
35
* @param loadingOption how to handle the initial file loading
35
36
* @param isWalkthrough whether to use sub-directories or not
36
- * @throws RuntimeException failure to walk through directory or failure to start watch service
37
+ * @throws UncheckedIOException failure to walk through directory or failure to start watch service
37
38
*
38
39
* @see FileBytesAdapter
39
40
* @see ByteLoadingOption
@@ -46,114 +47,56 @@ class DirectoryEntry {
46
47
this .loadingOption = loadingOption ;
47
48
this .isWalkthrough = isWalkthrough ;
48
49
50
+ directoryPath = directory .toPath ();
51
+
49
52
if (loadingOption == ByteLoadingOption .WATCHLOAD ){
50
- {
53
+ /* load top level directory */ {
51
54
final File [] listFiles = Objects .requireNonNullElse (directory .listFiles (),new File [0 ]);
52
- for (final File file : listFiles )
53
- if (! file .isDirectory ())
55
+ for (final File file : listFiles ) // initial population
56
+ if (file .isFile ())
54
57
try {
55
58
preloadedFiles .put (
56
59
getContext (adapter .getName (file )),
57
60
new FileEntry (file , adapter , ByteLoadingOption .WATCHLOAD , true )
58
61
);
59
62
}catch (final RuntimeException ignored ){ }
60
- try {
61
- final WatchService service = FileSystems .getDefault ().newWatchService ();
62
- final Path path = directory .toPath ();
63
- path .register (service , ENTRY_CREATE , ENTRY_DELETE , ENTRY_MODIFY );
64
-
65
- new Thread (() -> {
66
- WatchKey key ;
67
- try {
68
- while ((key = service .take ()) != null ){
69
- for (WatchEvent <?> event : key .pollEvents ()){
70
- try {
71
- final Path target = (Path ) event .context ();
72
- final File file = target .toFile ();
73
- final WatchEvent .Kind <?> type = event .kind ();
74
-
75
- final String context = getContext (adapter .getName (file ));
76
-
77
- if (type == ENTRY_CREATE )
78
- preloadedFiles .put (
79
- context ,
80
- new FileEntry (file , adapter , loadingOption ,true )
81
- );
82
- else if (type == ENTRY_DELETE )
83
- preloadedFiles .remove (context );
84
- else if (type == ENTRY_MODIFY )
85
- preloadedFiles .get (context ).reloadBytes ();
86
- }catch (final ClassCastException ignored ){ }
87
- }
88
- }
89
- }catch (final InterruptedException ignored ){ }
90
- }).start ();
91
-
63
+ try { // create watch service for top level directory
64
+ createWatchService (directoryPath , createWatchServiceConsumer ());
92
65
}catch (final IOException e ){
93
- throw new RuntimeException (e );
66
+ throw new UncheckedIOException (e );
94
67
}
95
68
}
96
- if (isWalkthrough ){
97
- final Path dirPath = directory .toPath ();
69
+ if (isWalkthrough ){ /* load sub directories */
98
70
try {
99
- Files .walk (dirPath ).filter (path -> path .toFile ().isDirectory ()).forEach (path -> {
71
+ Files .walk (directoryPath ).filter (path -> path .toFile ().isDirectory ()).forEach (path -> {
100
72
final File p2f = path .toFile ();
101
- final String rel = dirPath .relativize (path ).toString ();
73
+ final String rel = directoryPath .relativize (path ).toString ();
102
74
103
75
final File [] listFile = Objects .requireNonNullElse (p2f .listFiles (),new File [0 ]);
104
- for (final File file : listFile )
76
+ for (final File file : listFile ) // initial population
105
77
try {
106
78
preloadedFiles .put (
107
79
(rel .isEmpty () || rel .equals ("/" ) || rel .equals ("\\ " ) ? "" : getContext (rel )) + getContext (adapter .getName (file )),
108
80
new FileEntry (file ,adapter ,loadingOption ,true )
109
81
);
110
82
}catch (final RuntimeException ignored ){ }
111
83
112
- // watch service
84
+ // create watch service for directory
113
85
try {
114
- final WatchService service = FileSystems .getDefault ().newWatchService ();
115
- path .register (service , ENTRY_CREATE , ENTRY_DELETE , ENTRY_MODIFY );
116
- final String relative = getContext (dirPath .relativize (path ).toString ());
117
-
118
- new Thread (() -> {
119
- WatchKey key ;
120
- try {
121
- while ((key = service .take ()) != null ){
122
- for (WatchEvent <?> event : key .pollEvents ()){
123
- try {
124
- final Path target = dirPath .resolve ((Path ) event .context ());
125
- final File targFile = target .toFile ();
126
- final WatchEvent .Kind <?> type = event .kind ();
127
-
128
- final String context = relative + getContext (adapter .getName (targFile ));
129
-
130
- if (type == ENTRY_CREATE )
131
- preloadedFiles .put (
132
- context ,
133
- new FileEntry (targFile ,adapter ,loadingOption ,true )
134
- );
135
- else if (type == ENTRY_DELETE )
136
- preloadedFiles .remove (context );
137
- else if (type == ENTRY_MODIFY )
138
- preloadedFiles .get (context ).reloadBytes ();
139
- }catch (final ClassCastException ignored ){ }
140
- }
141
- key .reset ();
142
- }
143
- }catch (final InterruptedException ignored ){ }
144
- }).start ();
86
+ createWatchService (path , createWatchServiceConsumer ());
145
87
}catch (final IOException e ){
146
- throw new RuntimeException (e );
88
+ throw new UncheckedIOException (e );
147
89
}
90
+
148
91
});
149
92
}catch (final IOException e ){
150
93
throw new RuntimeException (e );
151
94
}
152
95
}
153
96
}else if (loadingOption == ByteLoadingOption .PRELOAD ){
154
- {
97
+ /* load top level directory */ {
155
98
final File [] listFiles = Objects .requireNonNullElse (directory .listFiles (),new File [0 ]);
156
- for (final File file : listFiles )
99
+ for (final File file : listFiles ) // initial population
157
100
if (!file .isDirectory ())
158
101
try {
159
102
preloadedFiles .put (
@@ -171,21 +114,78 @@ else if(type == ENTRY_MODIFY)
171
114
final String rel = dirPath .relativize (path ).toString ();
172
115
173
116
final File [] listFiles = Objects .requireNonNullElse (p2f .listFiles (), new File [0 ]);
174
- for (final File file : listFiles )
117
+ for (final File file : listFiles ) // populate sub files
175
118
try {
176
119
preloadedFiles .put (
177
120
(rel .isEmpty () || rel .equals ("/" ) || rel .equals ("\\ " ) ? "" : getContext (rel )) + getContext (adapter .getName (file )),
178
- new FileEntry (file , adapter , loadingOption )
121
+ new FileEntry (file , adapter , ByteLoadingOption . PRELOAD )
179
122
);
180
123
}catch (final RuntimeException ignored ){ }
181
124
});
182
125
}catch (final IOException e ){
183
- throw new RuntimeException (e );
126
+ throw new UncheckedIOException (e );
184
127
}
185
128
}
186
129
}
187
130
}
188
131
132
+
133
+ private final Map <Path ,Thread > watchService = new ConcurrentHashMap <>();
134
+
135
+ private void createWatchService (final Path path , final Consumer <WatchEvent <?>> consumer ) throws IOException {
136
+ final WatchService service = FileSystems .getDefault ().newWatchService ();
137
+ path .register (service ,ENTRY_CREATE ,ENTRY_DELETE ,ENTRY_MODIFY );
138
+
139
+ final Thread th =
140
+ new Thread (() -> {
141
+ WatchKey key ;
142
+ try {
143
+ while ((key = service .take ()) != null ){
144
+ for (WatchEvent <?> event : key .pollEvents ()){
145
+ consumer .accept (event );
146
+ key .reset ();
147
+ }
148
+ }
149
+ }catch (final InterruptedException ignored ){ }
150
+ });
151
+
152
+ watchService .put (path ,th );
153
+ }
154
+
155
+ @ SuppressWarnings ("StatementWithEmptyBody" )
156
+ private Consumer <WatchEvent <?>> createWatchServiceConsumer (){
157
+
158
+ return (WatchEvent <?> event ) -> {
159
+ try {
160
+ final Path target = (Path ) event .context ();
161
+ final File file = target .toFile ();
162
+ final WatchEvent .Kind <?> type = event .kind ();
163
+
164
+ final String relative = getContext (directoryPath .relativize (target ).toString ());
165
+ final String context = relative + getContext (adapter .getName (file ));
166
+
167
+ if (file .isFile ())
168
+ if (type == ENTRY_CREATE )
169
+ preloadedFiles .put (context , new FileEntry (file ,adapter ,ByteLoadingOption .WATCHLOAD ,true ));
170
+ else if (type == ENTRY_DELETE )
171
+ preloadedFiles .remove (context );
172
+ else if (type == ENTRY_MODIFY )
173
+ preloadedFiles .get (context ).reloadBytes ();
174
+ else ; // prevent ambiguous else with below
175
+ else
176
+ if (type == ENTRY_CREATE )
177
+ try {
178
+ createWatchService (target , createWatchServiceConsumer ());
179
+ }catch (final IOException ignored ){ }
180
+ else if (type == ENTRY_DELETE ){
181
+ watchService .get (target ).stop ();
182
+ watchService .remove (target );
183
+ }
184
+
185
+ }catch (final ClassCastException ignored ){ }
186
+ };
187
+ }
188
+
189
189
//
190
190
191
191
/**
0 commit comments