diff --git a/pom.xml b/pom.xml index b244a42..bd85f3a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.slavus.logcock logcock-app - 0.0.1-SNAPSHOT + 0.0.1 jar logcock-app @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.0.RELEASE + 2.7.2 @@ -71,12 +71,13 @@ org.apache.commons commons-lang3 - + org.webjars @@ -194,13 +195,7 @@ org.springframework.boot spring-boot-maven-plugin - - - org.springframework.boot.experimental - spring-boot-thin-layout - 1.0.24.RELEASE - - + diff --git a/src/main/java/net/slavus/logcock/files/FilesController.java b/src/main/java/net/slavus/logcock/files/FilesController.java index 9f12c95..0d62578 100644 --- a/src/main/java/net/slavus/logcock/files/FilesController.java +++ b/src/main/java/net/slavus/logcock/files/FilesController.java @@ -1,107 +1,134 @@ -package net.slavus.logcock.files; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.ResponseBody; - -import net.slavus.logcock.LogcockProperties; -import reactor.core.publisher.Mono; - -/** - * @author slavus - * - */ -@Controller -public class FilesController { - - @Autowired - private LogcockProperties properties; - - @GetMapping("/") - public String index() { - return "redirect:/b/"; - } - - @GetMapping("/b") - public String browseFolder(Model model) { - model.addAttribute("basePath", "/"); - model.addAttribute("baseId", ""); - model.addAttribute("filesInDir", properties.getFolders()); - model.addAttribute("breadcrumbs", new ArrayList<>()); - return "index"; - } - - @GetMapping("/b/{id}/{*webPath}") - public String browseFolder(@PathVariable Integer id, @PathVariable String webPath, Model model) { - String basePath = properties.getFolders().get(id).getBasePath(); - - String path = basePath + File.separator + webPath; - model.addAttribute("basePath", basePath); - model.addAttribute("baseId", id); - model.addAttribute("filesInDir", fileList(path)); - model.addAttribute("breadcrumbs", breadcrumbs(path, basePath)); - return "index"; - } - - @GetMapping("/d/{id}/{*webPath}") - @ResponseBody - private Mono> downloadFile(@PathVariable Integer id, @PathVariable String webPath, Model model) { - String basePath = properties.getFolders().get(id).getBasePath(); - String filePath = basePath + File.separator + webPath; - FileSystemResource fileSystemResource = new FileSystemResource(filePath); - return Mono.just(ResponseEntity - .ok() - .header("Content-Type", "application/octet-stream") - .header("Content-Disposition", "attachment; filename=" + fileSystemResource.getFilename()) - .body(fileSystemResource) - ); - } - - - private List breadcrumbs(String path, String basePath) { - final List crumbs = new ArrayList<>(); - File f = new File(path); - while(StringUtils.startsWith(f.getAbsolutePath(), basePath)) { - crumbs.add(f); - f = f.getParentFile(); - } - Collections.reverse(crumbs); - return crumbs; - } - - - public static List fileList(String path) { - File dir = new File(path); - if(!dir.exists()) { - return new ArrayList<>(); - } - List files = Arrays.stream(dir.listFiles()).sorted((o1, o2) -> { - boolean o1dir = o1.isDirectory(); - boolean o2dir = o2.isDirectory(); - - if (o1dir == o2dir) - return 0; - if (o1dir) - return -1; - if (o2dir) - return 1; - return 0; - }).collect(Collectors.toList()); - return dir.exists() ? files : new ArrayList<>(); - } - -} +package net.slavus.logcock.files; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseBody; + +import net.slavus.logcock.LogcockProperties; +import reactor.core.publisher.Mono; + +/** + * @author slavus + * + */ +@Controller +public class FilesController { + + @Autowired + private LogcockProperties properties; + + @Autowired + private ServerProperties serverProperties; + + @Autowired + Files files; + + @GetMapping("/") + public String index() { + return "redirect:/b/"; + } + + @GetMapping("/b") + public String browseFolder(Model model) { + model.addAttribute("basePath", "/"); + model.addAttribute("baseId", ""); + model.addAttribute("filesInDir", properties.getFolders()); + model.addAttribute("breadcrumbs", new ArrayList<>()); + return "index"; + } + + @GetMapping("/b/{id}/{*webPath}") + public String browseFolder(@PathVariable Integer id, @PathVariable String webPath, Model model) { + String basePath = properties.getFolders().get(id).getBasePath(); + + String path = basePath + File.separator + webPath; + model.addAttribute("basePath", basePath); + model.addAttribute("baseId", id); + model.addAttribute("filesInDir", fileList(path)); + model.addAttribute("breadcrumbs", breadcrumbs(path, basePath)); + return "index"; + } + + + @ResponseBody + @GetMapping(path = "/b/{id}/{*webPath}", consumes = MediaType.APPLICATION_JSON_VALUE) + public List jsonBrowseFolder(@PathVariable Integer id, @PathVariable String webPath, Model model, ServerHttpRequest req) { + String basePath = properties.getFolders().get(id).getBasePath(); + + String scheme = req.getURI().getScheme(); + String host = req.getHeaders().getHost().toString(); // includes server name and server port + + String serverPath = scheme + "://" + host + StringUtils.defaultString(serverProperties.getServlet().getContextPath()); + + String path = basePath + File.separator + webPath; + return fileList(path) + .stream().map(f->serverPath + "/d/" + id + files.linkPath(f, basePath)) + .collect(Collectors.toList()); + } + + + @GetMapping("/d/{id}/{*webPath}") + @ResponseBody + private Mono> downloadFile(@PathVariable Integer id, @PathVariable String webPath, Model model) { + String basePath = properties.getFolders().get(id).getBasePath(); + String filePath = basePath + File.separator + webPath; + FileSystemResource fileSystemResource = new FileSystemResource(filePath); + return Mono.just(ResponseEntity + .ok() + .header("Content-Type", "application/octet-stream") + .header("Content-Disposition", "attachment; filename=" + fileSystemResource.getFilename()) + .body(fileSystemResource) + ); + } + + + private List breadcrumbs(String path, String basePath) { + final List crumbs = new ArrayList<>(); + File f = new File(path); + while(StringUtils.startsWith(f.getAbsolutePath(), basePath)) { + crumbs.add(f); + f = f.getParentFile(); + } + Collections.reverse(crumbs); + return crumbs; + } + + + public static List fileList(String path) { + File dir = new File(path); + if(!dir.exists()) { + return new ArrayList<>(); + } + List files = Arrays.stream(dir.listFiles()).sorted((o1, o2) -> { + boolean o1dir = o1.isDirectory(); + boolean o2dir = o2.isDirectory(); + + if (o1dir == o2dir) + return 0; + if (o1dir) + return -1; + if (o2dir) + return 1; + return 0; + }).collect(Collectors.toList()); + return dir.exists() ? files : new ArrayList<>(); + } + +} diff --git a/src/main/java/net/slavus/logcock/tail/FileTailer.java b/src/main/java/net/slavus/logcock/tail/FileTailer.java index 9d4bfb7..9dddb30 100644 --- a/src/main/java/net/slavus/logcock/tail/FileTailer.java +++ b/src/main/java/net/slavus/logcock/tail/FileTailer.java @@ -1,122 +1,121 @@ -package net.slavus.logcock.tail; - -import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Lists; - -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.FluxSink; - -public class FileTailer{ - private static final Logger LOGGER = LoggerFactory.getLogger(FileTailer.class); - - public static void main(String[] args) throws IOException, InterruptedException { - FileTailer ft = new FileTailer(Executors.newFixedThreadPool(1), "logcock.log"); - Flux flux = ft.fileTailFlux(); - flux.subscribe(System.out::println); - } - - private long lastKnownPosition = 0; - private volatile boolean cancled = false; - File tailedFile; - private ExecutorService executorService; - - public FileTailer(ExecutorService executorService, String filename) { - this.executorService = executorService; - tailedFile = new File(filename); - } - - public Flux fileTailFlux() { - return Flux.create(sink -> { - Disposable onExit= () -> cancled = true; - - sink.onCancel(onExit); - sink.onDispose(onExit); - - //read initial lines - try { - List readLastLines = readLastLines(); - if (!readLastLines.isEmpty()) { - String lines = readLastLines.stream().collect(Collectors.joining("\n")); - LOGGER.debug("New lines: {}", lines); - sink.next(lines); - } - - } catch (IOException e) { - sink.error(e); - } - - executorService.execute(() -> { - try { - watchFile(sink); - } catch (InterruptedException | IOException e) { - sink.error(e); - } - }); - }); - } - - private void watchFile(FluxSink sink) throws IOException, InterruptedException { - WatchService watchService = FileSystems.getDefault().newWatchService(); - WatchKey watchedKey = tailedFile.getAbsoluteFile().getParentFile().toPath().register(watchService, ENTRY_MODIFY); - - WatchKey key; - while ((key = watchService.take()) != null) { - for (WatchEvent event : key.pollEvents()) { - if (Files.isSameFile(tailedFile.getAbsoluteFile().toPath(), (Path) event.context())) { - List readLastLines = readLastLines(); - if (!readLastLines.isEmpty()) { - String lines = readLastLines.stream().collect(Collectors.joining("\n")); - LOGGER.debug("New lines: {}", lines); - sink.next(lines); - } - } - } - key.reset(); - if (cancled) { - break; - } - } - watchedKey.cancel(); - } - - private List readLastLines() throws IOException { - List lines = Lists.newArrayList(); - - long fileLength = tailedFile.length(); - if (fileLength > lastKnownPosition) { - - // Reading and writing file - RandomAccessFile readWriteFileAccess; - readWriteFileAccess = new RandomAccessFile(tailedFile, "rw"); - readWriteFileAccess.seek(lastKnownPosition); - String crunentLine = null; - while ((crunentLine = readWriteFileAccess.readLine()) != null) { - lines.add(crunentLine); - } - lastKnownPosition = readWriteFileAccess.getFilePointer(); - readWriteFileAccess.close(); - - } - return lines; - } - -} +package net.slavus.logcock.tail; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import reactor.core.Disposable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; + +public class FileTailer{ + private static final Logger LOGGER = LoggerFactory.getLogger(FileTailer.class); + + public static void main(String[] args) throws IOException, InterruptedException { + FileTailer ft = new FileTailer(Executors.newFixedThreadPool(1), "logcock.log"); + Flux flux = ft.fileTailFlux(); + flux.subscribe(System.out::println); + } + + private long lastKnownPosition = 0; + private volatile boolean cancled = false; + File tailedFile; + private ExecutorService executorService; + + public FileTailer(ExecutorService executorService, String filename) { + this.executorService = executorService; + tailedFile = new File(filename); + } + + public Flux fileTailFlux() { + return Flux.create(sink -> { + Disposable onExit= () -> cancled = true; + + sink.onCancel(onExit); + sink.onDispose(onExit); + + //read initial lines + try { + List readLastLines = readLastLines(); + if (!readLastLines.isEmpty()) { + String lines = readLastLines.stream().collect(Collectors.joining("\n")); + LOGGER.debug("New lines: {}", lines); + sink.next(lines); + } + + } catch (IOException e) { + sink.error(e); + } + + executorService.execute(() -> { + try { + watchFile(sink); + } catch (InterruptedException | IOException e) { + sink.error(e); + } + }); + }); + } + + private void watchFile(FluxSink sink) throws IOException, InterruptedException { + WatchService watchService = FileSystems.getDefault().newWatchService(); + WatchKey watchedKey = tailedFile.getAbsoluteFile().getParentFile().toPath().register(watchService, ENTRY_MODIFY); + + WatchKey key; + while ((key = watchService.take()) != null) { + for (WatchEvent event : key.pollEvents()) { + if (Files.isSameFile(tailedFile.getAbsoluteFile().toPath(), (Path) event.context())) { + List readLastLines = readLastLines(); + if (!readLastLines.isEmpty()) { + String lines = readLastLines.stream().collect(Collectors.joining("\n")); + LOGGER.debug("New lines: {}", lines); + sink.next(lines); + } + } + } + key.reset(); + if (cancled) { + break; + } + } + watchedKey.cancel(); + } + + private List readLastLines() throws IOException { + List lines = new ArrayList<>(); + + long fileLength = tailedFile.length(); + if (fileLength > lastKnownPosition) { + + // Reading and writing file + RandomAccessFile readWriteFileAccess; + readWriteFileAccess = new RandomAccessFile(tailedFile, "rw"); + readWriteFileAccess.seek(lastKnownPosition); + String crunentLine = null; + while ((crunentLine = readWriteFileAccess.readLine()) != null) { + lines.add(crunentLine); + } + lastKnownPosition = readWriteFileAccess.getFilePointer(); + readWriteFileAccess.close(); + + } + return lines; + } + +} diff --git a/src/test/java/net/slavus/logcock/LogcockAppApplicationTests.java b/src/test/java/net/slavus/logcock/LogcockAppApplicationTests.java index f2e3859..d303b17 100644 --- a/src/test/java/net/slavus/logcock/LogcockAppApplicationTests.java +++ b/src/test/java/net/slavus/logcock/LogcockAppApplicationTests.java @@ -1,11 +1,8 @@ package net.slavus.logcock; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringRunner.class) @SpringBootTest public class LogcockAppApplicationTests {