Skip to content
This repository was archived by the owner on Jul 31, 2022. It is now read-only.

Commit ffcb3dd

Browse files
committed
Implement smart loading for directories
1 parent 5f7a198 commit ffcb3dd

File tree

3 files changed

+192
-70
lines changed

3 files changed

+192
-70
lines changed

src/main/java/com/kttdevelopment/simplehttpserver/handler/DirectoryEntry.java

Lines changed: 173 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.kttdevelopment.simplehttpserver.handler;
22

3-
import java.io.*;
4-
import java.nio.file.Files;
5-
import java.nio.file.Path;
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.*;
66
import java.util.*;
77
import java.util.concurrent.ConcurrentHashMap;
88

9+
import static java.nio.file.StandardWatchEventKinds.*;
10+
911
/**
1012
* Represents a directory in the {@link FileHandler}. Applications do not use this class.
1113
*
@@ -22,79 +24,176 @@ class DirectoryEntry {
2224
private final ByteLoadingOption loadingOption;
2325
private final boolean isWalkthrough;
2426

25-
private final Map<String,FileEntry> files = new ConcurrentHashMap<>(); // preload/watchload only
26-
27+
private final Map<String,FileEntry> preloadedFiles = new ConcurrentHashMap<>(); // preload/watchload only
2728

28-
DirectoryEntry(final File directory, final FileHandlerAdapter adapter, final ByteLoadingOption loadingOption, final boolean isWalkthrough) throws IOException{
29+
/**
30+
* Create a directory entry.
31+
*
32+
* @param directory directory to represent
33+
* @param adapter how to process the bytes in {@link #getBytes(String)}
34+
* @param loadingOption how to handle the initial file loading
35+
* @param isWalkthrough whether to use sub-directories or not
36+
* @throws RuntimeException failure to walk through directory or failure to start watch service
37+
*
38+
* @see FileBytesAdapter
39+
* @see ByteLoadingOption
40+
* @since 03.05.00
41+
* @author Ktt Development
42+
*/
43+
DirectoryEntry(final File directory, final FileHandlerAdapter adapter, final ByteLoadingOption loadingOption, final boolean isWalkthrough){
2944
this.directory = directory;
3045
this.adapter = adapter;
3146
this.loadingOption = loadingOption;
3247
this.isWalkthrough = isWalkthrough;
3348

3449
if(loadingOption == ByteLoadingOption.WATCHLOAD){
50+
{
51+
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(),new File[0]);
52+
for(final File file : listFiles)
53+
if(!file.isDirectory())
54+
try{
55+
preloadedFiles.put(
56+
getContext(adapter.getName(file)),
57+
new FileEntry(file, adapter, ByteLoadingOption.WATCHLOAD, true)
58+
);
59+
}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+
92+
}catch(final IOException e){
93+
throw new RuntimeException(e);
94+
}
95+
}
96+
if(isWalkthrough){
97+
final Path dirPath = directory.toPath();
98+
try{
99+
Files.walk(dirPath).filter(path -> path.toFile().isDirectory()).forEach(path -> {
100+
final File p2f = path.toFile();
101+
final String rel = dirPath.relativize(path).toString();
102+
103+
final File[] listFile = Objects.requireNonNullElse(p2f.listFiles(),new File[0]);
104+
for(final File file : listFile){
105+
try{
106+
preloadedFiles.put(
107+
(rel.isEmpty() || rel.equals("/") || rel.equals("\\") ? "" : getContext(rel)) + getContext(adapter.getName(file)),
108+
new FileEntry(file,adapter,loadingOption,true)
109+
);
110+
}catch(final RuntimeException ignored){ }
35111

112+
// watch service
113+
try{
114+
final WatchService service = FileSystems.getDefault().newWatchService();
115+
final Path dpath = file.toPath();
116+
dpath.register(service, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
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 = (Path) event.context();
125+
final File targFile = target.toFile();
126+
final WatchEvent.Kind<?> type = event.kind();
127+
128+
final String context = 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+
}
142+
}catch(final InterruptedException ignored){ }
143+
}).start();
144+
}catch(final IOException e){
145+
throw new RuntimeException(e);
146+
}
147+
}
148+
});
149+
}catch(final IOException e){
150+
throw new RuntimeException(e);
151+
}
152+
}
36153
}else if(loadingOption == ByteLoadingOption.PRELOAD){
37-
if(!isWalkthrough){
38-
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(),new File[0]);
39-
for(final File file : listFiles)
40-
if(!file.isDirectory())
41-
files.put(
154+
{
155+
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(),new File[0]);
156+
for(final File file : listFiles)
157+
if(!file.isDirectory())
158+
try{
159+
preloadedFiles.put(
42160
getContext(adapter.getName(file)),
43-
new FileEntry(file,adapter,ByteLoadingOption.PRELOAD)
161+
new FileEntry(file, adapter, ByteLoadingOption.PRELOAD)
44162
);
45-
}else{
46-
final Path dirPath = directory.toPath();
163+
}catch(final RuntimeException ignored){ }
164+
}
165+
if(isWalkthrough){
166+
final Path dirPath = directory.toPath();
47167

168+
try{
48169
Files.walk(dirPath).filter(path -> path.toFile().isDirectory()).forEach(path -> {
49170
final File p2f = path.toFile();
50171
final String rel = dirPath.relativize(path).toString();
51172

52173
final File[] listFiles = Objects.requireNonNullElse(p2f.listFiles(), new File[0]);
53174
for(final File file : listFiles)
54-
files.put(
55-
(rel.isEmpty() || rel.equals("/") || rel.equals("\\") ? "" : getContext(rel)) + getContext(adapter.getName(file)),
56-
new FileEntry(file,adapter,loadingOption)
57-
);
175+
try{
176+
preloadedFiles.put(
177+
(rel.isEmpty() || rel.equals("/") || rel.equals("\\") ? "" : getContext(rel)) + getContext(adapter.getName(file)),
178+
new FileEntry(file, adapter, loadingOption)
179+
);
180+
}catch(final RuntimeException ignored){ }
58181
});
182+
}catch(final IOException e){
183+
throw new RuntimeException(e);
59184
}
185+
}
60186
}
61187
}
62188

63-
//
64-
65-
/**
66-
* Returns if the directory uses inner folders and files
67-
*
68-
* @return if directory uses inner folders and files
69-
*
70-
* @since 02.00.00
71-
* @author Ktt Development
72-
*/
73-
public final boolean isWalkthrough(){
74-
return isWalkthrough;
75-
}
76-
77-
/**
78-
* Returns if the files were preloaded.
79-
*
80-
* @return if the files were preloaded
81-
*
82-
* @since 02.00.00
83-
* @author Ktt Development
84-
*/
85-
@Deprecated
86-
public final boolean isFilesPreloaded(){
87-
return loadingOption != ByteLoadingOption.LIVELOAD;
88-
}
89-
90189
//
91190

92191
/**
93192
* Returns the directory being referenced
94193
*
95194
* @return referenced directory
96195
*
97-
* @see #getFiles()
196+
* @see #getPreloadedFiles()
98197
* @see #getFile(String)
99198
* @since 02.00.00
100199
* @author Ktt Development
@@ -113,8 +212,8 @@ public final File getDirectory(){
113212
* @since 02.00.00
114213
* @author Ktt Development
115214
*/
116-
public Map<String, FileEntry> getFiles(){
117-
return files;
215+
public Map<String, FileEntry> getPreloadedFiles(){
216+
return Collections.unmodifiableMap(preloadedFiles);
118217
}
119218

120219
/**
@@ -124,18 +223,18 @@ public Map<String, FileEntry> getFiles(){
124223
* @return file associated with that context
125224
*
126225
* @see #getDirectory()
127-
* @see #getFiles()
226+
* @see #getPreloadedFiles()
128227
* @since 02.00.00
129228
* @author Ktt Development
130229
*/
131230
public final File getFile(final String path){
132231
final String rel = getContext(path);
133232
if(loadingOption != ByteLoadingOption.LIVELOAD){
134233
String match = "";
135-
for(final String key : files.keySet())
234+
for(final String key : preloadedFiles.keySet())
136235
if(rel.startsWith(key) && key.startsWith(match))
137236
match = key;
138-
return !match.isEmpty() ? files.get(match).getFile() : null;
237+
return !match.isEmpty() ? preloadedFiles.get(match).getFile() : null;
139238
}else{
140239
if(isWalkthrough){
141240
final File parent = new File(directory.getAbsolutePath() + path).getParentFile();
@@ -171,11 +270,11 @@ public final byte[] getBytes(final String path){
171270
final String rel = getContext(path);
172271
if(loadingOption != ByteLoadingOption.LIVELOAD){
173272
String match = "";
174-
for(final String key : files.keySet())
273+
for(final String key : preloadedFiles.keySet())
175274
if(rel.startsWith(key) && key.startsWith(match))
176275
match = key;
177276
if(!match.isEmpty()){
178-
return files.get(match).getBytes();
277+
return preloadedFiles.get(match).getBytes();
179278
}else{
180279
return null;
181280
}
@@ -199,8 +298,20 @@ public final byte[] getBytes(final String path){
199298
* @author Ktt Development
200299
*/
201300
public final ByteLoadingOption getLoadingOption(){
202-
return loadingOption;
203-
}
301+
return loadingOption;
302+
}
303+
304+
/**
305+
* Returns if the directory uses inner folders and files
306+
*
307+
* @return if directory uses inner folders and files
308+
*
309+
* @since 02.00.00
310+
* @author Ktt Development
311+
*/
312+
public final boolean isWalkthrough(){
313+
return isWalkthrough;
314+
}
204315

205316
//
206317

@@ -217,13 +328,13 @@ private static String getContext(final String path){
217328
@Override
218329
public String toString(){
219330
return
220-
"DirectoryEntry" + '{' +
221-
"directory" + '=' + directory + ", " +
222-
"adapter" + '=' + adapter + ", " +
223-
"loadingOption" + '=' + loadingOption + ", " +
224-
"isWalkthrough" + '=' + isWalkthrough + ", " +
225-
"files" + '=' + files +
226-
'}';
331+
"DirectoryEntry" + '{' +
332+
"directory" + '=' + directory + ", " +
333+
"adapter" + '=' + adapter + ", " +
334+
"loadingOption" + '=' + loadingOption + ", " +
335+
"isWalkthrough" + '=' + isWalkthrough + ", " +
336+
"files" + '=' + preloadedFiles +
337+
'}';
227338
}
228339

229340
}

src/main/java/com/kttdevelopment/simplehttpserver/handler/FileEntry.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ class FileEntry {
2828
* @param file file to represent
2929
* @param bytesAdapter how to process the bytes in {@link #getBytes()}
3030
* @param loadingOption how to handle the initial file loading
31-
* @throws IOException failure to start watch service ({@link ByteLoadingOption#WATCHLOAD} only).
31+
* @throws RuntimeException I/O failure to start watch service ({@link ByteLoadingOption#WATCHLOAD} only).
3232
*
3333
* @see FileBytesAdapter
3434
* @see ByteLoadingOption
3535
* @since 03.05.00
3636
* @author Ktt Development
3737
*/
38-
@SuppressWarnings("JavaDoc")
3938
FileEntry(final File file, final FileBytesAdapter bytesAdapter, final ByteLoadingOption loadingOption){
4039
this(file,bytesAdapter,loadingOption,true);
4140
}
@@ -47,14 +46,13 @@ class FileEntry {
4746
* @param bytesAdapter how to process the bytes in {@link #getBytes()}
4847
* @param loadingOption how to handle the initial file loading
4948
* @param skipWatchService skip creating a watch service ({@link ByteLoadingOption#WATCHLOAD} only).
50-
* @throws IOException failure to start watch service ({@link ByteLoadingOption#WATCHLOAD} only).
49+
* @throws RuntimeException I/O failure to start watch service ({@link ByteLoadingOption#WATCHLOAD} only).
5150
*
5251
* @see FileBytesAdapter
5352
* @see ByteLoadingOption
5453
* @since 03.05.00
5554
* @author Ktt Development
5655
*/
57-
@SuppressWarnings("JavaDoc")
5856
FileEntry(final File file, final FileBytesAdapter bytesAdapter, final ByteLoadingOption loadingOption, final boolean skipWatchService){
5957
this.file = file;
6058
this.loadingOption = loadingOption;
@@ -71,8 +69,8 @@ class FileEntry {
7169
new Thread(() -> {
7270
WatchKey key;
7371
try{
74-
while ((key = service.take()) != null) {
75-
for (WatchEvent<?> event : key.pollEvents()) {
72+
while((key = service.take()) != null){
73+
for(WatchEvent<?> event : key.pollEvents()){
7674
try{
7775
final Path target = (Path) event.context();
7876
try{
@@ -85,7 +83,7 @@ class FileEntry {
8583
}
8684
}catch(final InterruptedException ignored){ }
8785
}).start();
88-
}catch(IOException e){
86+
}catch(final IOException e){
8987
throw new RuntimeException(e);
9088
}
9189
case PRELOAD:
@@ -112,6 +110,19 @@ public final File getFile(){
112110
return file;
113111
}
114112

113+
/**
114+
* Reloads the file's preloaded bytes using the {@link FileBytesAdapter}.
115+
*
116+
* @since 03.05.00
117+
* @author Ktt Development
118+
*/
119+
public final void reloadBytes(){
120+
if(loadingOption != ByteLoadingOption.LIVELOAD)
121+
try{
122+
preloadedBytes = adapter.getBytes(file,Files.readAllBytes(file.toPath()));
123+
}catch(final IOException ignored){ }
124+
}
125+
115126
/**
116127
* Returns the file's bytes after the {@link FileBytesAdapter} was used.
117128
*

0 commit comments

Comments
 (0)