11package com .kttdevelopment .simplehttpserver .handler ;
22
3- import java .io .File ;
4- import java .io .IOException ;
3+ import java .io .*;
54import java .nio .file .*;
65import java .util .*;
76import java .util .concurrent .ConcurrentHashMap ;
7+ import java .util .function .Consumer ;
88
99import static java .nio .file .StandardWatchEventKinds .*;
1010
1414 * @see FileHandler
1515 * @see FileEntry
1616 * @since 02.00.00
17- * @version 03.05.00
17+ * @version 03.05.01
1818 * @author Ktt Development
1919 */
2020class DirectoryEntry {
@@ -25,6 +25,7 @@ class DirectoryEntry {
2525 private final boolean isWalkthrough ;
2626
2727 private final Map <String ,FileEntry > preloadedFiles = new ConcurrentHashMap <>(); // preload/watchload only
28+ private final Path directoryPath ;
2829
2930 /**
3031 * Create a directory entry.
@@ -33,7 +34,7 @@ class DirectoryEntry {
3334 * @param adapter how to process the bytes in {@link #getBytes(String)}
3435 * @param loadingOption how to handle the initial file loading
3536 * @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
3738 *
3839 * @see FileBytesAdapter
3940 * @see ByteLoadingOption
@@ -46,114 +47,56 @@ class DirectoryEntry {
4647 this .loadingOption = loadingOption ;
4748 this .isWalkthrough = isWalkthrough ;
4849
50+ directoryPath = directory .toPath ();
51+
4952 if (loadingOption == ByteLoadingOption .WATCHLOAD ){
50- {
53+ /* load top level directory */ {
5154 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 ())
5457 try {
5558 preloadedFiles .put (
5659 getContext (adapter .getName (file )),
5760 new FileEntry (file , adapter , ByteLoadingOption .WATCHLOAD , true )
5861 );
5962 }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 ());
9265 }catch (final IOException e ){
93- throw new RuntimeException (e );
66+ throw new UncheckedIOException (e );
9467 }
9568 }
96- if (isWalkthrough ){
97- final Path dirPath = directory .toPath ();
69+ if (isWalkthrough ){ /* load sub directories */
9870 try {
99- Files .walk (dirPath ).filter (path -> path .toFile ().isDirectory ()).forEach (path -> {
71+ Files .walk (directoryPath ).filter (path -> path .toFile ().isDirectory ()).forEach (path -> {
10072 final File p2f = path .toFile ();
101- final String rel = dirPath .relativize (path ).toString ();
73+ final String rel = directoryPath .relativize (path ).toString ();
10274
10375 final File [] listFile = Objects .requireNonNullElse (p2f .listFiles (),new File [0 ]);
104- for (final File file : listFile )
76+ for (final File file : listFile ) // initial population
10577 try {
10678 preloadedFiles .put (
10779 (rel .isEmpty () || rel .equals ("/" ) || rel .equals ("\\ " ) ? "" : getContext (rel )) + getContext (adapter .getName (file )),
10880 new FileEntry (file ,adapter ,loadingOption ,true )
10981 );
11082 }catch (final RuntimeException ignored ){ }
11183
112- // watch service
84+ // create watch service for directory
11385 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 ());
14587 }catch (final IOException e ){
146- throw new RuntimeException (e );
88+ throw new UncheckedIOException (e );
14789 }
90+
14891 });
14992 }catch (final IOException e ){
15093 throw new RuntimeException (e );
15194 }
15295 }
15396 }else if (loadingOption == ByteLoadingOption .PRELOAD ){
154- {
97+ /* load top level directory */ {
15598 final File [] listFiles = Objects .requireNonNullElse (directory .listFiles (),new File [0 ]);
156- for (final File file : listFiles )
99+ for (final File file : listFiles ) // initial population
157100 if (!file .isDirectory ())
158101 try {
159102 preloadedFiles .put (
@@ -171,21 +114,78 @@ else if(type == ENTRY_MODIFY)
171114 final String rel = dirPath .relativize (path ).toString ();
172115
173116 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
175118 try {
176119 preloadedFiles .put (
177120 (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 )
179122 );
180123 }catch (final RuntimeException ignored ){ }
181124 });
182125 }catch (final IOException e ){
183- throw new RuntimeException (e );
126+ throw new UncheckedIOException (e );
184127 }
185128 }
186129 }
187130 }
188131
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+
189189//
190190
191191 /**
0 commit comments