Skip to content
Snippets Groups Projects
Commit d71c5542 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Node listing implementation

parent 58f6570b
Branches
Tags
No related merge requests found
......@@ -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
}
}
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;
}
}
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;
}
}
.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");
}
......@@ -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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment