Commit d71c5542 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Node listing implementation

parent 58f6570b
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -29,4 +29,9 @@ public class NodesController {

        nodesService.generateNodesHtml(path, response.getOutputStream());
    }

    @GetMapping(value = "/download/{path}")
    public void directDownload(@PathVariable("path") String path) {
        // TODO: call pullFromVoSpace sync transfer
    }
}
+127 −0
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui.service;

import it.inaf.ia2.vospace.ui.VOSpaceException;
import java.util.Optional;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NodeInfo {

    private static final Logger LOG = LoggerFactory.getLogger(NodeInfo.class);

    private final String authority;

    private final Node node;

    private final String path;
    private final String name;
    private final String size;
    private final String groupRead;
    private final String groupWrite;
    private final boolean isPublic;

    public NodeInfo(Node node, String authority) {
        this.authority = authority;
        this.node = node;
        this.path = getPath(node);
        this.name = path.substring(path.lastIndexOf("/") + 1);
        this.size = getSize(node);
        this.groupRead = getGroupRead(node);
        this.groupWrite = getGroupWrite(node);
        this.isPublic = isPublic(node);
    }

    private String getPath(Node node) {

        String uri = node.getUri();

        String prefix = "vos://" + authority;

        if (!uri.startsWith(prefix)) {
            throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri);
        }

        return uri.substring(prefix.length());
    }

    private String getGroupRead(Node node) {
        return getProperty(node, "ivo://ivoa.net/vospace/core#groupread").orElse("");
    }

    private String getGroupWrite(Node node) {
        return getProperty(node, "ivo://ivoa.net/vospace/core#groupwrite").orElse("");
    }

    private boolean isPublic(Node node) {
        return Boolean.parseBoolean(getProperty(node, "ivo://ivoa.net/vospace/core#ispublic").orElse("false"));
    }

    private Optional<String> getProperty(Node node, String uri) {
        if (node.getProperties() != null && node.getProperties().getProperty() != null) {
            for (Property property : node.getProperties().getProperty()) {
                if (uri.equals(property.getUri())) {
                    return Optional.of(property.getValue());
                }
            }
        }
        return Optional.empty();
    }

    private String getSize(Node node) {
        return getProperty(node, "ivo://ivoa.net/vospace/core#length")
                .map(value -> {
                    try {
                        long bytes = Long.parseLong(value);
                        return getHumanReadableSize(bytes);
                    } catch (NumberFormatException ex) {
                        LOG.warn("Invalid length for node " + node.getUri() + ". Length is " + value);
                        return "";
                    }
                })
                .orElse("");
    }

    /**
     * Credits: https://stackoverflow.com/a/16576773/771431
     */
    private String getHumanReadableSize(long bytes) {
        int u = 0;
        for (; bytes > 1024 * 1024; bytes >>= 10) {
            u++;
        }
        if (bytes > 1024) {
            u++;
        }
        return String.format("%.1f %cB", bytes / 1024f, " kMGTPE".charAt(u));
    }

    public String getType() {
        return node.getType();
    }

    public String getPath() {
        return path;
    }

    public String getName() {
        return name;
    }

    public String getSize() {
        return size;
    }

    public String getGroupRead() {
        return groupRead;
    }

    public String getGroupWrite() {
        return groupWrite;
    }

    public boolean isPublic() {
        return isPublic;
    }
}
+37 −12
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui.service;

import it.inaf.ia2.vospace.ui.VOSpaceException;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import java.io.OutputStream;
import java.io.PrintWriter;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -13,6 +14,8 @@ import org.springframework.stereotype.Service;
@Service
public class NodesService {

    private static final Logger LOG = LoggerFactory.getLogger(NodesService.class);

    @Autowired
    private VOSpaceClient client;

@@ -37,24 +40,46 @@ public class NodesService {
    }

    private String getNodeHtml(Node node) {

        NodeInfo nodeInfo = new NodeInfo(node, authority);

        String html = "<tr>";
        html += "<td><a href=\"#\">";
        html += getName(node);
        html += "</a></td>";
        html += "<td><input type=\"checkbox\" data-node=\"" + nodeInfo.getPath() + "\" /></td>";
        html += "<td>" + getIcon(nodeInfo) + getLink(nodeInfo) + "</td>";
        html += "<td>" + nodeInfo.getSize() + "</td>";
        html += "<td>" + nodeInfo.getGroupRead() + "</td>";
        html += "<td>" + nodeInfo.getGroupWrite() + "</td>";
        html += "</tr>";
        return html;
    }

    private String getName(Node node) {

        String uri = node.getUri();

        String prefix = "vos://" + authority;
    private String getIcon(NodeInfo nodeInfo) {
        String html = "<span class=\"icon ";
        if ("vos:ContainerNode".equals(nodeInfo.getType())) {
            html += "folder";
        } else {
            html += "file";
        }
        html += "-icon\"></span>&nbsp;";
        return html;
    }

        if (!uri.startsWith(prefix)) {
            throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri);
    private String getLink(NodeInfo nodeInfo) {
        if (isDownloadable(nodeInfo)) {
            if ("vos:ContainerNode".equals(nodeInfo.getType())) {
                return "<a href=\"#nodes" + nodeInfo.getPath() + "\">" + nodeInfo.getName() + "</a>";
            } else {
                return "<a href=\"download" + nodeInfo.getPath() + "\" target=\"blank_\">" + nodeInfo.getName() + "</a>";
            }
        }
        return nodeInfo.getName();
    }

        return uri.substring(prefix.length() + 1);
    private boolean isDownloadable(NodeInfo nodeInfo) {
        if (nodeInfo.isPublic()) {
            return true;
        }
        // TODO: check user group
        return false;
    }
}
+14 −0
Original line number Diff line number Diff line
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
}

.folder-icon {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='folder' xmlns='http://www.w3.org/2000/svg' fill='currentColor' %3E%3Cg%3E%3Cpath fill-rule='evenodd' d='M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v.64c.57.265.94.876.856 1.546l-.64 5.124A2.5 2.5 0 0 1 12.733 15H3.266a2.5 2.5 0 0 1-2.481-2.19l-.64-5.124A1.5 1.5 0 0 1 1 6.14V3.5zM2 6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5a.5.5 0 0 0-.5.5V6zm-.367 1a.5.5 0 0 0-.496.562l.64 5.124A1.5 1.5 0 0 0 3.266 14h9.468a1.5 1.5 0 0 0 1.489-1.314l.64-5.124A.5.5 0 0 0 14.367 7H1.633z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
}

.file-icon {
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='file earmark' xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='bi-file-earmark b-icon bi'%3E%3Cg%3E%3Cpath d='M4 0h5.5v1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h1V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z'%3E%3C/path%3E%3Cpath d='M9.5 3V0L14 4.5h-3A1.5 1.5 0 0 1 9.5 3z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
}
+38 −1
Original line number Diff line number Diff line
@@ -4,7 +4,15 @@
    <table class="table b-table table-striped table-hover">
      <thead>
        <tr>
          <th>File</th>
          <th id="checkboxes">
            <a href="#" @click.stop.prevent="selectAll"><BIconCheckSquare /></a>
            &nbsp;
            <a href="#" @click.stop.prevent="deSelectAll"><BIconSquare /></a>
          </th>
          <th>Name</th>
          <th>Size</th>
          <th>Group read</th>
          <th>Group write</th>
        </tr>
      </thead>
      <tbody id="nodes"></tbody>
@@ -15,13 +23,42 @@

<script>
import client from 'api-client';
import { BIconCheckSquare, BIconSquare } from 'bootstrap-vue'

export default {
  components: {
    BIconCheckSquare, BIconSquare
  },
  mounted() {
    client.getNode(this.$store.state.path)
      .then(res => {
        document.getElementById('nodes').outerHTML = res;
      });
  },
  methods: {
    selectAll() {
      this.selectInputs(true);
    },
    deSelectAll() {
        this.selectInputs(false);
    },
    selectInputs(value) {
      document.querySelectorAll('#nodes input').forEach(input => input.checked = value);
    }
  }
}
</script>

<style>
@import '../assets/css/fonts.css';

.table {
  text-align: left;
}

th#checkboxes {
  /* Credits: https://stackoverflow.com/a/43615091/771431 */
  width:0.1%;
  white-space: nowrap;
}
</style>