From e3c5fe291d765c3d2170cbf56497de155f1b83b5 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 22 Mar 2020 02:13:23 +0000 Subject: [PATCH] WIP on file details page --- .../com/muwire/webui/FileInfoServlet.java | 191 ++++++++++++++++++ .../src/main/java/com/muwire/webui/Util.java | 3 + webui/src/main/js/fileDetails.js | 119 +++++++++++ webui/src/main/js/files.js | 4 +- webui/src/main/webapp/FileDetails.jsp | 58 ++++++ webui/templates/web.xml.template | 15 ++ 6 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 webui/src/main/java/com/muwire/webui/FileInfoServlet.java create mode 100644 webui/src/main/js/fileDetails.js create mode 100644 webui/src/main/webapp/FileDetails.jsp diff --git a/webui/src/main/java/com/muwire/webui/FileInfoServlet.java b/webui/src/main/java/com/muwire/webui/FileInfoServlet.java new file mode 100644 index 00000000..77f51fc7 --- /dev/null +++ b/webui/src/main/java/com/muwire/webui/FileInfoServlet.java @@ -0,0 +1,191 @@ +package com.muwire.webui; + +import java.io.File; +import java.io.IOException; +import java.text.Collator; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.muwire.core.Core; +import com.muwire.core.InfoHash; +import com.muwire.core.Persona; +import com.muwire.core.SharedFile; +import com.muwire.core.filecert.Certificate; + +import net.i2p.data.DataHelper; + +public class FileInfoServlet extends HttpServlet { + + private Core core; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String path = req.getParameter("path"); + if (path == null) { + resp.sendError(403, "bad param"); + return; + } + File file = Util.getFromPathElements(path); + SharedFile sf = core.getFileManager().getFileToSharedFile().get(file); + if (sf == null) { + resp.sendError(403, file + " is not shared"); + return; + } + + String section = req.getParameter("section"); + if (section == null) { + resp.sendError(403, "Bad param"); + return; + } + + StringBuilder sb = new StringBuilder(); + sb.append(""); + + if (section.equals("searchers")) { + List searchEntries = sf.getSearches().stream(). + map(e -> new SearchEntry(e)). + collect(Collectors.toList()); + SEARCH_ENTRY_COMPARATORS.sort(searchEntries, req); + sb.append(""); + searchEntries.forEach(e -> e.toXML(sb)); + sb.append(""); + } else if (section.equals("downloaders")) { + List downloadEntries = sf.getDownloaders().stream(). + map(d -> new DownloadEntry(d)). + collect(Collectors.toList()); + DOWNLOADER_COMPARATORS.sort(downloadEntries, req); + sb.append(""); + downloadEntries.forEach(e -> e.toXML(sb)); + sb.append(""); + } else if (section.equals("certificates")) { + List certificateEntries = core.getCertificateManager().getByInfoHash(new InfoHash(sf.getRoot())).stream(). + map(c -> new CertificateEntry(c)). + collect(Collectors.toList()); + CERT_COMPARATORS.sort(certificateEntries, req); + sb.append(""); + certificateEntries.forEach(e -> e.toXML(sb)); + sb.append(""); + } else { + resp.sendError(403, "Bad param"); + return; + } + + resp.setContentType("text/xml"); + resp.setCharacterEncoding("UTF-8"); + resp.setDateHeader("Expires", 0); + resp.setHeader("Pragma", "no-cache"); + resp.setHeader("Cache-Control", "no-store, max-age=0, no-cache, must-revalidate"); + byte[] out = sb.toString().getBytes("UTF-8"); + resp.setContentLength(out.length); + resp.getOutputStream().write(out); + } + + @Override + public void init(ServletConfig config) throws ServletException { + core = (Core) config.getServletContext().getAttribute("core"); + } + + private static class SearchEntry { + private final Persona persona; + private final long timestamp; + private final String query; + + SearchEntry(SharedFile.SearchEntry e) { + this.persona = e.getSearcher(); + this.timestamp = e.getTimestamp(); + this.query = e.getQuery(); + } + + void toXML(StringBuilder sb) { + sb.append(""); + sb.append("").append(Util.escapeHTMLinXML(persona.getHumanReadableName())).append(""); + sb.append("").append(DataHelper.formatTime(timestamp)).append(""); + sb.append("").append(Util.escapeHTMLinXML(query)).append(""); + sb.append(""); + } + } + + private static final Comparator BY_SEARCHER = (l, r) -> { + return Collator.getInstance().compare(l.persona.getHumanReadableName(), r.persona.getHumanReadableName()); + }; + + private static final Comparator BY_TIMESTAMP = (l, r) -> { + return Long.compare(l.timestamp, r.timestamp); + }; + + private static final Comparator BY_QUERY = (l, r) -> { + return Collator.getInstance().compare(l.query, r.query); + }; + + private static final ColumnComparators SEARCH_ENTRY_COMPARATORS = new ColumnComparators<>(); + static { + SEARCH_ENTRY_COMPARATORS.add("Searcher", BY_SEARCHER); + SEARCH_ENTRY_COMPARATORS.add("Timestamp", BY_TIMESTAMP); + SEARCH_ENTRY_COMPARATORS.add("Query", BY_QUERY); + } + + private static class DownloadEntry { + private final String downloader; + DownloadEntry(String downloader) { + this.downloader = downloader; + } + + void toXML(StringBuilder sb) { + sb.append(""); + sb.append("").append(Util.escapeHTMLinXML(downloader)).append(""); + sb.append(""); + } + } + + private static final Comparator BY_DOWNLOADER = (l, r) -> { + return Collator.getInstance().compare(l.downloader, r.downloader); + }; + + private static final ColumnComparators DOWNLOADER_COMPARATORS = new ColumnComparators<>(); + static { + DOWNLOADER_COMPARATORS.add("Downloader", BY_DOWNLOADER); + } + + private static class CertificateEntry { + private final Certificate certificate; + CertificateEntry(Certificate certificate) { + this.certificate = certificate; + } + + void toXML(StringBuilder sb) { + sb.append(""); + sb.append("").append(Util.escapeHTMLinXML(certificate.getName().getName())).append(""); + if (certificate.getComment() != null) + sb.append("").append(Util.escapeHTMLinXML(certificate.getComment().getName())).append(""); + sb.append("").append(DataHelper.formatTime(certificate.getTimestamp())).append(""); + sb.append("").append(Util.escapeHTMLinXML(certificate.getIssuer().getHumanReadableName())).append(""); + sb.append(""); + } + } + + private static final Comparator CERT_BY_NAME = (l, r) -> { + return Collator.getInstance().compare(l.certificate.getName().getName(), r.certificate.getName().getName()); + }; + + private static final Comparator CERT_BY_TIMESTAMP = (l, r) -> { + return Long.compare(l.certificate.getTimestamp(), r.certificate.getTimestamp()); + }; + + private static final Comparator CERT_BY_ISSUER = (l, r) -> { + return Collator.getInstance().compare(l.certificate.getIssuer().getHumanReadableName(), r.certificate.getIssuer().getHumanReadableName()); + }; + + private static final ColumnComparators CERT_COMPARATORS = new ColumnComparators<>(); + static { + CERT_COMPARATORS.add("Name", CERT_BY_NAME); + CERT_COMPARATORS.add("Timestamp", CERT_BY_TIMESTAMP); + CERT_COMPARATORS.add("Issuer", CERT_BY_ISSUER); + } +} diff --git a/webui/src/main/java/com/muwire/webui/Util.java b/webui/src/main/java/com/muwire/webui/Util.java index 36c4f8c6..f533f0d3 100644 --- a/webui/src/main/java/com/muwire/webui/Util.java +++ b/webui/src/main/java/com/muwire/webui/Util.java @@ -108,11 +108,13 @@ public class Util { _x("Retry"), _x("Save"), _x("Search"), + _x("Searcher"), _x("Sender"), _x("Senders"), _x("Shared Files"), _x("Sharing"), _x("Show Comment"), + _x("Show Details"), _x("Size"), _x("Sources"), _x("Speed"), @@ -122,6 +124,7 @@ public class Util { _x("Subscribe"), _x("Subscribed"), _x("Times Browsed"), + _x("Timestamp"), _x("Total Pieces"), _x("Trust"), _x("Trusted"), diff --git a/webui/src/main/js/fileDetails.js b/webui/src/main/js/fileDetails.js new file mode 100644 index 00000000..a923589c --- /dev/null +++ b/webui/src/main/js/fileDetails.js @@ -0,0 +1,119 @@ +class SearchEntry { + constructor(xmlNode) { + this.persona = xmlNode.getElementsByTagName("Persona")[0].childNodes[0].nodeValue + this.timestamp = xmlNode.getElementsByTagName("Timestamp")[0].childNodes[0].nodeValue + this.query = xmlNode.getElementsByTagName("Query")[0].childNodes[0].nodeValue + } + + getMapping() { + var mapping = new Map() + mapping.set("Searcher", this.persona) + mapping.set("Timestamp", this.timestamp) + mapping.set("Query", this.query) + return mapping + } +} + +class DownloadEntry { + constructor(xmlNode) { + this.persona = xmlNode.getElementsByTagName("Persona")[0].childNodes[0].nodeValue + } + + getMapping() { + var mapping = new Map() + mapping.set("Downloader", this.persona) + return mapping + } +} + +class CertificateEntry { + constructor(xmlNode) { + this.name = xmlNode.getElementsByTagName("Name")[0].childNodes[0].nodeValue + this.timestamp = xmlNode.getElementsByTagName("Timestamp")[0].childNodes[0].nodeValue + this.issuer = xmlNode.getElementsByTagName("Issuer")[0].childNodes[0].nodeValue + try { + this.comment = xmlNode.getElementsByTagName("Comment")[0].childNodes[0].nodeValue + } catch (ignored) { + this.comment = null + } + } + + getMapping() { + var mapping = new Map() + // TODO: comments + mapping.set("Name", this.name) + mapping.set("Timestamp", this.timestamp) + mapping.set("Issuer", this.issuer) + return mapping + } +} + +function initFileDetails() { + setTimeout(refreshAll, 1) + setInterval(refreshAll, 3000) +} + +function refreshAll() { + refreshSearchers() + refreshDownloaders() + refreshCertificates() +} + +function refreshSearchers() { + var xmlhttp = new XMLHttpRequest() + xmlhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + var searchers = [] + var searchNodes = this.responseXML.getElementsByTagName("SearchEntry") + var i + for (i = 0; i < searchNodes.length; i++) { + searchers.push(new SearchEntry(searchNodes[i])) + } + + var newOrder + if (searchersSortOrder == "descending") + newOrder = "ascending" + else if (searchersSortOrder == "ascending") + newOrder = "descending" + var table = new Table(["Searcher", "Timestamp", "Query"], "sortSearchers", searchersSortKey, newOrder, null) + + for (i = 0; i < searchers.length; i++) { + table.addRow(searchers[i].getMapping()) + } + + var hitsDiv = document.getElementById("hitsTable") + if (searchNodes.length > 0) + hitsDiv.innerHTML = table.render() + else + hitsDiv.innerHTML = "" + } + } + var sortParam = "&key=" + searchersSortKey + "&order=" + searchersSortOrder + xmlhttp.open("GET", encodeURI("/MuWire/FileInfo?path=" + path + "§ion=searchers" + sortParam)) + xmlhttp.send() +} + +function refreshDownloaders() { + +} + +function refreshCertificates() { + +} + +function sortSearchers(key, order) { + searchersSortKey = key + searchersSortOrder = order + refreshSearchers() +} + +var path = null + +var expandedComments = new Map() + +var searchersSortKey = "searcher" +var searchersSortOrder = "descending" +var downloadersSortKey = "downloader" +var downloadersSortOrder = "descending" +var certificatesSortKey = "name" +var certificatesSortOrder = "descending" diff --git a/webui/src/main/js/files.js b/webui/src/main/js/files.js index e5e7df36..ece9c8d6 100644 --- a/webui/src/main/js/files.js +++ b/webui/src/main/js/files.js @@ -49,10 +49,12 @@ class Node { var infoHashTextArea = "" var copyInfoHashLink = new Link(_t("Copy hash to clipboard"), "copyAndAlert", [this.infoHash, _t("Hash copied to clipboard")]) + var detailsLink = "" + _t("Show Details") + "" + var nameLink = "" + this.path + "" var html = "
  • " + nameLink + infoHashTextArea html += "
    " + certified + " " + published + "
    " html += "
    " html += "
  • " diff --git a/webui/src/main/webapp/FileDetails.jsp b/webui/src/main/webapp/FileDetails.jsp new file mode 100644 index 00000000..469c4a20 --- /dev/null +++ b/webui/src/main/webapp/FileDetails.jsp @@ -0,0 +1,58 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%@ page import="com.muwire.webui.*" %> +<%@ page import="java.io.*" %> +<%@include file="initcode.jsi"%> + +<% + +String pagetitle=Util._t("File Details"); + +String path = request.getParameter("path"); +File file = Util.getFromPathElements(path); + +%> + + + +<%@ include file="css.jsi"%> + + + + + + + + +<%@ include file="header.jsi"%> + +
    +

    <%=Util._t("Details for {0}", file.getAbsolutePath())%>

    +

    <%=Util._t("Search Hits")%>

    +
    +
    +
    +
    +
    +
    +

    <%=Util._t("Downloaders")%>

    +
    +
    +
    +
    +
    +
    +

    <%=Util._t("Certificates")%>

    +
    +
    +
    +
    +
    +
    + + diff --git a/webui/templates/web.xml.template b/webui/templates/web.xml.template index c0ffd6f5..8d6bfe4c 100644 --- a/webui/templates/web.xml.template +++ b/webui/templates/web.xml.template @@ -76,6 +76,11 @@ com.muwire.webui.StatusServlet + + com.muwire.webui.FileInfoServlet + com.muwire.webui.FileInfoServlet + + com.muwire.webui.MuWireServlet /index.jsp @@ -141,6 +146,11 @@ /Status + + com.muwire.webui.FileInfoServlet + /FileInfo + + __JASPER__