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

Commit 0959218

Browse files
authored
File Handler directory & walk fixes (#64)
* bugs * bug fixes Co-authored-by: Katsute <[email protected]>
1 parent c8ded90 commit 0959218

File tree

6 files changed

+82
-62
lines changed

6 files changed

+82
-62
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
* @see FileHandler
1111
* @since 03.05.00
12-
* @version 03.05.00
12+
* @version 03.05.01
1313
* @author Ktt Development
1414
*/
1515
@SuppressWarnings("SpellCheckingInspection")

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

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import java.io.*;
44
import java.nio.file.*;
5+
import java.nio.file.attribute.BasicFileAttributes;
56
import java.util.*;
67
import java.util.concurrent.ConcurrentHashMap;
8+
import java.util.concurrent.atomic.AtomicBoolean;
79
import java.util.function.Consumer;
810

911
import static java.nio.file.StandardWatchEventKinds.*;
@@ -50,52 +52,51 @@ class DirectoryEntry {
5052
directoryPath = directory.toPath();
5153

5254
if(loadingOption == ByteLoadingOption.WATCHLOAD){
53-
/* load top level directory */ {
54-
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(),new File[0]);
55-
for(final File file : listFiles) // initial population
56-
if(file.isFile())
55+
if(!isWalkthrough){ // load top level only
56+
for(final File file : Objects.requireNonNullElse(directory.listFiles(), new File[0])) // initial population
57+
if(!file.isDirectory()) // File#isFile does not work
5758
try{
5859
preloadedFiles.put(
5960
getContext(adapter.getName(file)),
6061
new FileEntry(file, adapter, ByteLoadingOption.WATCHLOAD, true)
6162
);
62-
}catch(final RuntimeException ignored){ }
63+
}catch(final UncheckedIOException ignored){ }
6364
try{ // create watch service for top level directory
64-
createWatchService(directoryPath, createWatchServiceConsumer());
65+
createWatchService(directoryPath, createWatchServiceConsumer(directoryPath));
6566
}catch(final IOException e){
6667
throw new UncheckedIOException(e);
6768
}
68-
}
69-
if(isWalkthrough){ /* load sub directories */
69+
}else{ // load top and sub levels
7070
try{
71-
Files.walk(directoryPath).filter(path -> path.toFile().isDirectory()).forEach(path -> {
72-
final File p2f = path.toFile();
73-
final String rel = directoryPath.relativize(path).toString();
71+
Files.walkFileTree(directoryPath, new SimpleFileVisitor<>() {
72+
@Override
73+
public final FileVisitResult preVisitDirectory(final Path path, final BasicFileAttributes attrs) throws IOException{
74+
createWatchService(path, createWatchServiceConsumer(path));
75+
return FileVisitResult.CONTINUE;
76+
}
77+
78+
@Override
79+
public final FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs){
80+
final File file = path.toFile();
81+
final String relative = directoryPath.relativize(path.getParent()).toString(); // attach the relative path (parent) to the adapted file name
7482

75-
final File[] listFile = Objects.requireNonNullElse(p2f.listFiles(),new File[0]);
76-
for(final File file : listFile) // initial population
7783
try{
7884
preloadedFiles.put(
79-
(rel.isEmpty() || rel.equals("/") || rel.equals("\\") ? "" : getContext(rel)) + getContext(adapter.getName(file)),
80-
new FileEntry(file,adapter,loadingOption,true)
85+
joinContext(relative, adapter.getName(file)),
86+
new FileEntry(file, adapter, loadingOption, true)
8187
);
82-
}catch(final RuntimeException ignored){ }
88+
}catch(final UncheckedIOException ignored){ }
8389

84-
// create watch service for directory
85-
try{
86-
createWatchService(path, createWatchServiceConsumer());
87-
}catch(final IOException e){
88-
throw new UncheckedIOException(e);
90+
return FileVisitResult.CONTINUE;
8991
}
90-
9192
});
9293
}catch(final IOException e){
9394
throw new UncheckedIOException(e);
9495
}
9596
}
9697
}else if(loadingOption == ByteLoadingOption.PRELOAD){
97-
/* load top level directory */ {
98-
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(),new File[0]);
98+
if(!isWalkthrough){ // load top level only
99+
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(), new File[0]);
99100
for(final File file : listFiles) // initial population
100101
if(!file.isDirectory())
101102
try{
@@ -104,21 +105,23 @@ class DirectoryEntry {
104105
new FileEntry(file, adapter, ByteLoadingOption.PRELOAD)
105106
);
106107
}catch(final UncheckedIOException ignored){ }
107-
}
108-
if(isWalkthrough){ /* load sub directories */
108+
}else{ // load top and sub levels
109109
try{
110-
Files.walk(directoryPath).filter(path -> path.toFile().isDirectory()).forEach(path -> {
111-
final File p2f = path.toFile();
112-
final String relative = directoryPath.relativize(path).toString();
110+
Files.walkFileTree(directoryPath, new SimpleFileVisitor<>() {
111+
@Override
112+
public final FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs){
113+
final File file = path.toFile();
114+
final String relative = directoryPath.relativize(path.getParent()).toString(); // attach the relative path (parent) to the adapted file name
113115

114-
final File[] listFiles = Objects.requireNonNullElse(p2f.listFiles(), new File[0]);
115-
for(final File file : listFiles) // populate sub files
116116
try{
117117
preloadedFiles.put(
118-
(relative.isEmpty() || relative.equals("/") || relative.equals("\\") ? "" : getContext(relative)) + getContext(adapter.getName(file)),
118+
joinContext(relative,adapter.getName(file)),
119119
new FileEntry(file, adapter, ByteLoadingOption.PRELOAD)
120120
);
121121
}catch(final RuntimeException ignored){ }
122+
123+
return FileVisitResult.CONTINUE;
124+
}
122125
});
123126
}catch(final IOException e){
124127
throw new UncheckedIOException(e);
@@ -128,60 +131,64 @@ class DirectoryEntry {
128131
}
129132

130133

131-
private final Map<Path,Thread> watchService = new ConcurrentHashMap<>();
134+
private final Map<Path,AtomicBoolean> watchService = new ConcurrentHashMap<>();
132135

133136
private void createWatchService(final Path path, final Consumer<WatchEvent<?>> consumer) throws IOException{
134137
final WatchService service = FileSystems.getDefault().newWatchService();
135138
path.register(service,ENTRY_CREATE,ENTRY_DELETE,ENTRY_MODIFY);
136139

137-
final Thread th =
140+
final AtomicBoolean stop = new AtomicBoolean(false);
141+
138142
new Thread(() -> {
139143
WatchKey key;
140144
try{
141145
while((key = service.take()) != null){
142-
for(WatchEvent<?> event : key.pollEvents()){
146+
for(WatchEvent<?> event : key.pollEvents())
143147
consumer.accept(event);
144-
key.reset();
145-
}
148+
key.reset();
149+
if(stop.get())
150+
break;
146151
}
147152
}catch(final InterruptedException ignored){ }
148-
});
149-
150-
th.start();
153+
}).start();
151154

152-
watchService.put(path,th);
155+
watchService.put(path,stop);
153156
}
154157

155-
@SuppressWarnings("StatementWithEmptyBody")
156-
private Consumer<WatchEvent<?>> createWatchServiceConsumer(){
158+
@SuppressWarnings({"StatementWithEmptyBody", "SpellCheckingInspection"})
159+
private Consumer<WatchEvent<?>> createWatchServiceConsumer(final Path path){
157160
return (WatchEvent<?> event) -> {
158161
try{
159-
final Path target = (Path) event.context();
160-
final File file = target.toFile();
162+
final Path relTarg = directoryPath.resolve((Path) event.context()); // only the file name (this method is flawed!)
163+
final File relFile = relTarg.toFile(); // only the file name (this method if flawed!)
161164
final WatchEvent.Kind<?> type = event.kind();
162165

163-
final String relative = getContext(directoryPath.relativize(target).toString());
164-
final String context = (relative.isEmpty() || relative.equals("/") || relative.equals("\\") ? "" : getContext(relative)) + getContext(adapter.getName(file));
166+
final String top2sub = getContext(directoryPath.relativize(path).toString()); // the relative path between the top level directory and sub directory
167+
final String context = joinContext(top2sub,adapter.getName(relFile)); // the file key
168+
final File file = new File(directoryPath + joinContext(top2sub,relFile.getName())); // the actual referable file
169+
final Path target = file.toPath();
165170

166-
if(file.isFile())
171+
if(!file.isDirectory()) // File#isFile does not work
167172
if(type == ENTRY_CREATE)
168173
preloadedFiles.put(context, new FileEntry(file,adapter,ByteLoadingOption.WATCHLOAD,true));
169174
else if(type == ENTRY_DELETE)
170175
preloadedFiles.remove(context);
171176
else if(type == ENTRY_MODIFY)
172-
preloadedFiles.get(context).reloadBytes();
177+
Objects.requireNonNull(preloadedFiles.get(context)).reloadBytes();
173178
else; // prevent ambiguous else with below
174-
else
179+
else if(isWalkthrough) // only add/remove if walkthrough
175180
if(type == ENTRY_CREATE)
176181
try{
177-
createWatchService(target, createWatchServiceConsumer());
182+
createWatchService(target, createWatchServiceConsumer(target));
178183
}catch(final IOException ignored){ }
179184
else if(type == ENTRY_DELETE){
180-
watchService.get(target).stop();
181-
watchService.remove(target);
185+
Objects.requireNonNull(watchService.get(relTarg)).set(true);
186+
watchService.remove(relTarg);
182187
}
183188

184-
}catch(final ClassCastException ignored){ }
189+
preloadedFiles.get(context).reloadBytes();
190+
191+
}catch(final ClassCastException | NullPointerException ignored){ }
185192
};
186193
}
187194

@@ -293,13 +300,23 @@ public final boolean isWalkthrough(){
293300

294301
//
295302

303+
// leading slash only
296304
private static String getContext(final String path){
297305
final String linSlash = path.replace("\\","/");
298306
if(linSlash.equals("/")) return "/";
299307
final String seSlash = (!linSlash.startsWith("/") ? "/" : "") + linSlash + (!linSlash.endsWith("/") ? "/" : "");
300308
return seSlash.substring(0,seSlash.length()-1);
301309
}
302310

311+
private static String joinContext(final String... paths){
312+
final StringBuilder OUT = new StringBuilder();
313+
for(final String path : paths){
314+
final String context = getContext(path);
315+
OUT.append(context.isEmpty() || context.equals("/") || context.equals("\\") ? "" : context);
316+
}
317+
return OUT.toString();
318+
}
319+
303320
//
304321

305322

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
class FileEntry {
1717

1818
private final File file;
19-
@SuppressWarnings("FieldCanBeLocal")
2019
private final FileBytesAdapter adapter;
2120
private final ByteLoadingOption loadingOption;
2221

@@ -121,7 +120,9 @@ public final void reloadBytes(){
121120
if(loadingOption != ByteLoadingOption.LIVELOAD)
122121
try{
123122
preloadedBytes = adapter.getBytes(file,Files.readAllBytes(file.toPath()));
124-
}catch(final IOException ignored){ }
123+
}catch(final IOException e){
124+
preloadedBytes = null;
125+
}
125126
}
126127

127128
/**
@@ -138,8 +139,8 @@ public final byte[] getBytes(){
138139
return preloadedBytes; // adapter determined preloaded bytes
139140
else
140141
try{
141-
return adapter.getBytes(file,Files.readAllBytes(file.toPath())); // return literal bytes & adapt them
142-
}catch(final IOException e){
142+
return adapter.getBytes(file,Files.readAllBytes(file.toPath())); // read and adapt bytes
143+
}catch(final IOException ignored){
143144
return null;
144145
}
145146
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.sun.net.httpserver.HttpExchange;
77

88
import java.io.*;
9+
import java.net.URLDecoder;
10+
import java.nio.charset.StandardCharsets;
911
import java.util.*;
1012
import java.util.concurrent.ConcurrentHashMap;
1113

@@ -633,7 +635,7 @@ public final void addDirectory(final String context, final File directory, final
633635

634636
@Override
635637
public final void handle(final SimpleHttpExchange exchange) throws IOException{
636-
final String context = getContext(exchange.getURI().getPath().substring(exchange.getHttpContext().getPath().length()));
638+
final String context = URLDecoder.decode(getContext(exchange.getURI().getPath().substring(exchange.getHttpContext().getPath().length())), StandardCharsets.UTF_8);
637639

638640
if(files.containsKey(context)){ // exact file match
639641
final FileEntry entry = files.get(context);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* @see SessionThrottler
1818
* @see ServerSessionThrottler
1919
* @since 03.05.00
20-
* @version 03.05.00
20+
* @version 03.05.01
2121
* @author Ktt Development
2222
*/
2323
public class ServerExchangeThrottler extends ConnectionThrottler {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @see ServerExchangeThrottler
2020
* @see SessionThrottler
2121
* @since 03.05.00
22-
* @version 03.05.00
22+
* @version 03.05.01
2323
* @author Ktt Development
2424
*/
2525
public class ServerSessionThrottler extends ConnectionThrottler{

0 commit comments

Comments
 (0)