From f0bf6c5d7c4c085e6e23bf949d100e06405c569e Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 9 Apr 2019 12:06:38 +0200 Subject: [PATCH] Add index and search to documentation app --- .../java/ctbrec/docs/AbstractDocServlet.java | 106 + .../src/main/java/ctbrec/docs/DocServer.java | 6 + .../java/ctbrec/docs/MarkdownServlet.java | 70 +- .../main/java/ctbrec/docs/SearchServlet.java | 49 + client/src/main/resources/html/docs/400.html | 3 + client/src/main/resources/html/docs/404.html | 2 + .../resources/html/docs/RunningTheServer.md | 4 +- .../src/main/resources/html/docs/footer.html | 11 + .../src/main/resources/html/docs/header.html | 38 +- client/src/main/resources/html/docs/index.md | 14 - .../main/resources/html/static/freelancer.css | 382 + .../resources/html/static/freelancer.min.css | 1 - .../vendor/jquery-ui/jquery-ui-1.12.1.css | 1311 ++ .../vendor/jquery-ui/jquery-ui-1.12.1.js | 18706 ++++++++++++++++ 14 files changed, 20631 insertions(+), 72 deletions(-) create mode 100644 client/src/main/java/ctbrec/docs/AbstractDocServlet.java create mode 100644 client/src/main/java/ctbrec/docs/SearchServlet.java create mode 100644 client/src/main/resources/html/docs/400.html create mode 100644 client/src/main/resources/html/docs/404.html delete mode 100644 client/src/main/resources/html/docs/index.md create mode 100644 client/src/main/resources/html/static/freelancer.css delete mode 100644 client/src/main/resources/html/static/freelancer.min.css create mode 100644 client/src/main/resources/html/static/vendor/jquery-ui/jquery-ui-1.12.1.css create mode 100644 client/src/main/resources/html/static/vendor/jquery-ui/jquery-ui-1.12.1.js diff --git a/client/src/main/java/ctbrec/docs/AbstractDocServlet.java b/client/src/main/java/ctbrec/docs/AbstractDocServlet.java new file mode 100644 index 00000000..fb3e0d7d --- /dev/null +++ b/client/src/main/java/ctbrec/docs/AbstractDocServlet.java @@ -0,0 +1,106 @@ +package ctbrec.docs; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Objects; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractDocServlet extends HttpServlet { + + private static final transient Logger LOG = LoggerFactory.getLogger(AbstractDocServlet.class); + + String loadFile(String resource) throws IOException { + InputStream resourceAsStream = getClass().getResourceAsStream(resource); + if(resourceAsStream == null) { + throw new FileNotFoundException(); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int length = 0; + byte[] buffer = new byte[1024]; + while( (length = resourceAsStream.read(buffer)) >= 0 ) { + out.write(buffer, 0, length); + } + return new String(out.toByteArray(), "utf-8"); + } + + String getHeader() throws IOException { + return loadFile("/html/docs/header.html"); + } + + String getFooter() throws IOException { + return loadFile("/html/docs/footer.html"); + } + + List getPages() throws IOException { + List pages = new ArrayList<>(); + URL resource = getClass().getResource("/html/docs"); + if(Objects.equals(resource.getProtocol(), "file")) { + LOG.debug("FILE {}", resource.toString()); + indexDirectory(resource, pages); + } else if(Objects.equals(resource.getProtocol(), "jar")) { + LOG.debug("JAR {}", resource.toString()); + indexJar(resource, pages); + } + pages.add("index.md"); + Collections.sort(pages, (a, b) -> a.compareToIgnoreCase(b)); + return pages; + } + + private void indexJar(URL resource, List pages) throws IOException { + String fileUrl = resource.getFile(); + int colon = fileUrl.indexOf(':'); + int exclamation = fileUrl.indexOf('!'); + String jar = fileUrl.substring(colon + 1, exclamation); + String internalFile = fileUrl.substring(exclamation + 2); + try (JarFile jarFile = new JarFile(jar)) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry jarEntry = entries.nextElement(); + String name = jarEntry.getName(); + if (name.startsWith(internalFile) && name.toLowerCase().endsWith(".md")) { + pages.add(name.substring(name.lastIndexOf('/') + 1)); + } + } + } + } + + private void indexDirectory(URL resource, List pages) { + File docs = new File(resource.getFile()); + String[] files = docs.list((dir, name) -> name.toLowerCase().endsWith(".md")); + for (String file : files) { + pages.add(file); + } + } + + String loadMarkdown(String path) throws IOException { + String resource = "/html" + path; + return loadFile(resource); + } + + protected void error(HttpServletResponse resp, int status, String message) throws IOException { + resp.setStatus(status); + resp.getWriter().println(getHeader()); + String html = loadFile("/html/docs/" + status + ".html"); + if(message == null || message.trim().isEmpty()) { + message = ""; + } + html = html.replace("{message}", message); + resp.getWriter().println(html); + resp.getWriter().println(getFooter()); + } +} diff --git a/client/src/main/java/ctbrec/docs/DocServer.java b/client/src/main/java/ctbrec/docs/DocServer.java index 1d1057e9..141e6bb6 100644 --- a/client/src/main/java/ctbrec/docs/DocServer.java +++ b/client/src/main/java/ctbrec/docs/DocServer.java @@ -41,10 +41,16 @@ public class DocServer { MarkdownServlet markdownServlet = new MarkdownServlet(); ServletHolder holder = new ServletHolder(markdownServlet); handler.addServletWithMapping(holder, "/docs/*"); + + AbstractDocServlet searchServlet = new SearchServlet(); + holder = new ServletHolder(searchServlet); + handler.addServletWithMapping(holder, "/search/*"); + StaticFileServlet staticFileServlet = new StaticFileServlet(); holder = new ServletHolder(staticFileServlet); handler.addServletWithMapping(holder, "/static/*"); + try { server.start(); started = true; diff --git a/client/src/main/java/ctbrec/docs/MarkdownServlet.java b/client/src/main/java/ctbrec/docs/MarkdownServlet.java index c93e7535..8dd99b25 100644 --- a/client/src/main/java/ctbrec/docs/MarkdownServlet.java +++ b/client/src/main/java/ctbrec/docs/MarkdownServlet.java @@ -1,46 +1,59 @@ package ctbrec.docs; -import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; +import java.util.List; import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Objects; import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.options.MutableDataSet; -public class MarkdownServlet extends HttpServlet { +public class MarkdownServlet extends AbstractDocServlet { private static final transient Logger LOG = LoggerFactory.getLogger(MarkdownServlet.class); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String path = req.getRequestURI(); - LOG.debug("Path: [{}]", path); + LOG.trace("Path: [{}]", path); try { - String md = loadMarkdown(path); - String html = markdownToHtml(md); - resp.setStatus(HttpServletResponse.SC_OK); - resp.getWriter().println(getHeader()); - resp.getWriter().println(html); - resp.getWriter().println(getFooter()); + if(Objects.equal(path, "/docs/index.md")) { + listPages(resp); + } else { + String md = loadMarkdown(path); + String html = markdownToHtml(md); + resp.setStatus(HttpServletResponse.SC_OK); + resp.getWriter().println(getHeader()); + resp.getWriter().println(html); + resp.getWriter().println(getFooter()); + } } catch (FileNotFoundException e) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - resp.setContentType("text/plain"); - resp.getWriter().println("Hö?"); - resp.getWriter().println(); + error(resp, HttpServletResponse.SC_NOT_FOUND, ""); } } + private void listPages(HttpServletResponse resp) throws IOException { + List pages = getPages(); + String html = "
    "; + for (String page : pages) { + html += "
  • " + page + "
  • "; + } + html += "
"; + resp.setStatus(HttpServletResponse.SC_OK); + resp.getWriter().println(getHeader()); + resp.getWriter().println(html); + resp.getWriter().println(getFooter()); + } + private String markdownToHtml(String markdown) { MutableDataSet options = new MutableDataSet(); Parser parser = Parser.builder(options).build(); @@ -49,31 +62,4 @@ public class MarkdownServlet extends HttpServlet { String html = renderer.render(document); return html; } - - private String loadMarkdown(String path) throws IOException { - String resource = "/html" + path; - return loadFile(resource); - } - - private String loadFile(String resource) throws IOException { - InputStream resourceAsStream = getClass().getResourceAsStream(resource); - if(resourceAsStream == null) { - throw new FileNotFoundException(); - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int length = 0; - byte[] buffer = new byte[1024]; - while( (length = resourceAsStream.read(buffer)) >= 0 ) { - out.write(buffer, 0, length); - } - return new String(out.toByteArray(), "utf-8"); - } - - private String getHeader() throws IOException { - return loadFile("/html/docs/header.html"); - } - - private String getFooter() throws IOException { - return loadFile("/html/docs/footer.html"); - } } diff --git a/client/src/main/java/ctbrec/docs/SearchServlet.java b/client/src/main/java/ctbrec/docs/SearchServlet.java new file mode 100644 index 00000000..dacf073d --- /dev/null +++ b/client/src/main/java/ctbrec/docs/SearchServlet.java @@ -0,0 +1,49 @@ +package ctbrec.docs; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONArray; + + +public class SearchServlet extends AbstractDocServlet { + private static final String Q = "term"; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if(req.getParameter(Q) == null) { + error(resp, HttpServletResponse.SC_BAD_REQUEST, "Parameter \""+Q+"\" is missing"); + return; + } + + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("application/json"); + JSONArray result = new JSONArray(); + List pages = getPages(); + + String q = req.getParameter(Q).toLowerCase(); + String[] tokens = q.split("\\s+"); + for (String page : pages) { + try { + String content = loadMarkdown("/docs/" + page).toLowerCase(); + boolean allFound = true; + for (String token : tokens) { + if(!content.contains(token)) { + allFound = false; + } + } + if(allFound) { + result.put(page); + } + } catch(FileNotFoundException e) { + // virtual page like index.md -> ignore + } + } + resp.getWriter().println(result.toString()); + } +} diff --git a/client/src/main/resources/html/docs/400.html b/client/src/main/resources/html/docs/400.html new file mode 100644 index 00000000..ea1f9c45 --- /dev/null +++ b/client/src/main/resources/html/docs/400.html @@ -0,0 +1,3 @@ +

400 Bad Request

+

{message}

+Try something else! \ No newline at end of file diff --git a/client/src/main/resources/html/docs/404.html b/client/src/main/resources/html/docs/404.html new file mode 100644 index 00000000..6cae96d4 --- /dev/null +++ b/client/src/main/resources/html/docs/404.html @@ -0,0 +1,2 @@ +

404 File Not Found

+Try something else! \ No newline at end of file diff --git a/client/src/main/resources/html/docs/RunningTheServer.md b/client/src/main/resources/html/docs/RunningTheServer.md index ca8b294b..ac3076c7 100644 --- a/client/src/main/resources/html/docs/RunningTheServer.md +++ b/client/src/main/resources/html/docs/RunningTheServer.md @@ -1,5 +1,5 @@ How To Run The Server -======================= +------------ The archive you downloaded contains a `server.bat` or `server.sh`, which can be used to start the server. On the first start, the server uses a default configuration. Once you terminate the server by pressing ctrl + c, the config is stored in your user home. On Windows that is `C:\Users\{your user name}\AppData\Roaming\ctbrec\server.json` @@ -8,4 +8,4 @@ On Linux it is `~/.config/ctbrec/server.json` On macOS it is `/Users/{your user name}/Library/Preferences/ctbrec` -You can open this file in a text editor and change it to your likings. You probably only want to change httpPort and recordingsDir. Most of the other stuff is irrelevant since the server and CTB Recorder use the same config file format. When the server is running, you can connect to it with CTB Recorder by changing the setting "Record location" to "Remote". \ No newline at end of file +You can open this file in a text editor and change it to your likings. You probably only want to change `httpPort` and `recordingsDir`. Most of the other stuff is irrelevant since the server and CTB Recorder use the same config file format. When the server is running, you can connect to it with CTB Recorder by changing the setting "Record location" to "Remote". \ No newline at end of file diff --git a/client/src/main/resources/html/docs/footer.html b/client/src/main/resources/html/docs/footer.html index 96fba82e..694625d2 100644 --- a/client/src/main/resources/html/docs/footer.html +++ b/client/src/main/resources/html/docs/footer.html @@ -59,11 +59,22 @@ + + + diff --git a/client/src/main/resources/html/docs/header.html b/client/src/main/resources/html/docs/header.html index 9ec2684b..ebd4d59c 100644 --- a/client/src/main/resources/html/docs/header.html +++ b/client/src/main/resources/html/docs/header.html @@ -29,32 +29,47 @@ + - + + + -