Browse Source

Upgraded rendering to support identifiers, as well as single file resources.

This allows any QDN resource (e.g. an IMAGE) to be linked to from a website/app and then rendered on screen. It isn't yet supported in gateway or domain map mode, as these need some more thought.
qdn-on-chain-data
CalDescent 2 years ago
parent
commit
37b20aac66
  1. 8
      src/main/java/org/qortal/api/domainmap/resource/DomainMapResource.java
  2. 14
      src/main/java/org/qortal/api/gateway/resource/GatewayResource.java
  3. 19
      src/main/java/org/qortal/api/restricted/resource/RenderResource.java
  4. 20
      src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java
  5. 3
      src/main/resources/loading/index.html
  6. 17
      src/main/resources/q-apps/q-apps.js

8
src/main/java/org/qortal/api/domainmap/resource/DomainMapResource.java

@ -42,15 +42,15 @@ public class DomainMapResource {
// Build synchronously, so that we don't need to make the summary API endpoints available over // Build synchronously, so that we don't need to make the summary API endpoints available over
// the domain map server. This means that there will be no loading screen, but this is potentially // the domain map server. This means that there will be no loading screen, but this is potentially
// preferred in this situation anyway (e.g. to avoid confusing search engine robots). // preferred in this situation anyway (e.g. to avoid confusing search engine robots).
return this.get(domainMap.get(request.getServerName()), ResourceIdType.NAME, Service.WEBSITE, inPath, null, "", false, false); return this.get(domainMap.get(request.getServerName()), ResourceIdType.NAME, Service.WEBSITE, null, inPath, null, "", false, false);
} }
return ArbitraryDataRenderer.getResponse(response, 404, "Error 404: File Not Found"); return ArbitraryDataRenderer.getResponse(response, 404, "Error 404: File Not Found");
} }
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
String secret58, String prefix, boolean usePrefix, boolean async) { String inPath, String secret58, String prefix, boolean usePrefix, boolean async) {
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, inPath, ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
secret58, prefix, usePrefix, async, "domainMap", request, response, context); secret58, prefix, usePrefix, async, "domainMap", request, response, context);
return renderer.render(); return renderer.render();
} }

14
src/main/java/org/qortal/api/gateway/resource/GatewayResource.java

@ -82,7 +82,7 @@ public class GatewayResource {
@PathParam("path") String inPath) { @PathParam("path") String inPath) {
// Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data
Security.disallowLoopbackRequests(request); Security.disallowLoopbackRequests(request);
return this.get(name, ResourceIdType.NAME, Service.WEBSITE, inPath, null, "", true, true); return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, inPath, null, "", true, true);
} }
@GET @GET
@ -91,7 +91,7 @@ public class GatewayResource {
public HttpServletResponse getIndexByName(@PathParam("name") String name) { public HttpServletResponse getIndexByName(@PathParam("name") String name) {
// Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data
Security.disallowLoopbackRequests(request); Security.disallowLoopbackRequests(request);
return this.get(name, ResourceIdType.NAME, Service.WEBSITE, "/", null, "", true, true); return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, "/", null, "", true, true);
} }
@ -103,7 +103,7 @@ public class GatewayResource {
@PathParam("path") String inPath) { @PathParam("path") String inPath) {
// Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data
Security.disallowLoopbackRequests(request); Security.disallowLoopbackRequests(request);
return this.get(name, ResourceIdType.NAME, Service.WEBSITE, inPath, null, "/site", true, true); return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, inPath, null, "/site", true, true);
} }
@GET @GET
@ -111,14 +111,14 @@ public class GatewayResource {
public HttpServletResponse getSiteIndexByName(@PathParam("name") String name) { public HttpServletResponse getSiteIndexByName(@PathParam("name") String name) {
// Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data
Security.disallowLoopbackRequests(request); Security.disallowLoopbackRequests(request);
return this.get(name, ResourceIdType.NAME, Service.WEBSITE, "/", null, "/site", true, true); return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, "/", null, "/site", true, true);
} }
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
String secret58, String prefix, boolean usePrefix, boolean async) { String inPath, String secret58, String prefix, boolean usePrefix, boolean async) {
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, inPath, ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
secret58, prefix, usePrefix, async, "gateway", request, response, context); secret58, prefix, usePrefix, async, "gateway", request, response, context);
return renderer.render(); return renderer.render();
} }

19
src/main/java/org/qortal/api/restricted/resource/RenderResource.java

@ -146,7 +146,7 @@ public class RenderResource {
if (!Settings.getInstance().isQDNAuthBypassEnabled()) if (!Settings.getInstance().isQDNAuthBypassEnabled())
Security.requirePriorAuthorization(request, signature, Service.WEBSITE, null); Security.requirePriorAuthorization(request, signature, Service.WEBSITE, null);
return this.get(signature, ResourceIdType.SIGNATURE, null, "/", null, "/render/signature", true, true, theme); return this.get(signature, ResourceIdType.SIGNATURE, null, null, "/", null, "/render/signature", true, true, theme);
} }
@GET @GET
@ -157,7 +157,7 @@ public class RenderResource {
if (!Settings.getInstance().isQDNAuthBypassEnabled()) if (!Settings.getInstance().isQDNAuthBypassEnabled())
Security.requirePriorAuthorization(request, signature, Service.WEBSITE, null); Security.requirePriorAuthorization(request, signature, Service.WEBSITE, null);
return this.get(signature, ResourceIdType.SIGNATURE, null, inPath,null, "/render/signature", true, true, theme); return this.get(signature, ResourceIdType.SIGNATURE, null, null, inPath,null, "/render/signature", true, true, theme);
} }
@GET @GET
@ -168,7 +168,7 @@ public class RenderResource {
if (!Settings.getInstance().isQDNAuthBypassEnabled()) if (!Settings.getInstance().isQDNAuthBypassEnabled())
Security.requirePriorAuthorization(request, hash58, Service.WEBSITE, null); Security.requirePriorAuthorization(request, hash58, Service.WEBSITE, null);
return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, "/", secret58, "/render/hash", true, false, theme); return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, null, "/", secret58, "/render/hash", true, false, theme);
} }
@GET @GET
@ -180,7 +180,7 @@ public class RenderResource {
if (!Settings.getInstance().isQDNAuthBypassEnabled()) if (!Settings.getInstance().isQDNAuthBypassEnabled())
Security.requirePriorAuthorization(request, hash58, Service.WEBSITE, null); Security.requirePriorAuthorization(request, hash58, Service.WEBSITE, null);
return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, inPath, secret58, "/render/hash", true, false, theme); return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, null, inPath, secret58, "/render/hash", true, false, theme);
} }
@GET @GET
@ -189,12 +189,13 @@ public class RenderResource {
public HttpServletResponse getPathByName(@PathParam("service") Service service, public HttpServletResponse getPathByName(@PathParam("service") Service service,
@PathParam("name") String name, @PathParam("name") String name,
@PathParam("path") String inPath, @PathParam("path") String inPath,
@QueryParam("identifier") String identifier,
@QueryParam("theme") String theme) { @QueryParam("theme") String theme) {
if (!Settings.getInstance().isQDNAuthBypassEnabled()) if (!Settings.getInstance().isQDNAuthBypassEnabled())
Security.requirePriorAuthorization(request, name, service, null); Security.requirePriorAuthorization(request, name, service, null);
String prefix = String.format("/render/%s", service); String prefix = String.format("/render/%s", service);
return this.get(name, ResourceIdType.NAME, service, inPath, null, prefix, true, true, theme); return this.get(name, ResourceIdType.NAME, service, identifier, inPath, null, prefix, true, true, theme);
} }
@GET @GET
@ -207,15 +208,15 @@ public class RenderResource {
Security.requirePriorAuthorization(request, name, service, null); Security.requirePriorAuthorization(request, name, service, null);
String prefix = String.format("/render/%s", service); String prefix = String.format("/render/%s", service);
return this.get(name, ResourceIdType.NAME, service, "/", null, prefix, true, true, theme); return this.get(name, ResourceIdType.NAME, service, null, "/", null, prefix, true, true, theme);
} }
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
String secret58, String prefix, boolean usePrefix, boolean async, String theme) { String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String theme) {
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, inPath, ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
secret58, prefix, usePrefix, async, "render", request, response, context); secret58, prefix, usePrefix, async, "render", request, response, context);
if (theme != null) { if (theme != null) {

20
src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java

@ -2,6 +2,7 @@ package org.qortal.arbitrary;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.qortal.api.HTMLParser; import org.qortal.api.HTMLParser;
@ -34,6 +35,7 @@ public class ArbitraryDataRenderer {
private final String resourceId; private final String resourceId;
private final ResourceIdType resourceIdType; private final ResourceIdType resourceIdType;
private final Service service; private final Service service;
private final String identifier;
private String theme = "light"; private String theme = "light";
private String inPath; private String inPath;
private final String secret58; private final String secret58;
@ -45,13 +47,14 @@ public class ArbitraryDataRenderer {
private final HttpServletResponse response; private final HttpServletResponse response;
private final ServletContext context; private final ServletContext context;
public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
String secret58, String prefix, boolean usePrefix, boolean async, String qdnContext, String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String qdnContext,
HttpServletRequest request, HttpServletResponse response, ServletContext context) { HttpServletRequest request, HttpServletResponse response, ServletContext context) {
this.resourceId = resourceId; this.resourceId = resourceId;
this.resourceIdType = resourceIdType; this.resourceIdType = resourceIdType;
this.service = service; this.service = service;
this.identifier = identifier != null ? identifier : "default";
this.inPath = inPath; this.inPath = inPath;
this.secret58 = secret58; this.secret58 = secret58;
this.prefix = prefix; this.prefix = prefix;
@ -73,14 +76,14 @@ public class ArbitraryDataRenderer {
return ArbitraryDataRenderer.getResponse(response, 500, "QDN is disabled in settings"); return ArbitraryDataRenderer.getResponse(response, 500, "QDN is disabled in settings");
} }
ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(resourceId, resourceIdType, service, null); ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(resourceId, resourceIdType, service, identifier);
arbitraryDataReader.setSecret58(secret58); // Optional, used for loading encrypted file hashes only arbitraryDataReader.setSecret58(secret58); // Optional, used for loading encrypted file hashes only
try { try {
if (!arbitraryDataReader.isCachedDataAvailable()) { if (!arbitraryDataReader.isCachedDataAvailable()) {
// If async is requested, show a loading screen whilst build is in progress // If async is requested, show a loading screen whilst build is in progress
if (async) { if (async) {
arbitraryDataReader.loadAsynchronously(false, 10); arbitraryDataReader.loadAsynchronously(false, 10);
return this.getLoadingResponse(service, resourceId, theme); return this.getLoadingResponse(service, resourceId, identifier, theme);
} }
// Otherwise, loop until we have data // Otherwise, loop until we have data
@ -113,6 +116,12 @@ public class ArbitraryDataRenderer {
} }
String unzippedPath = path.toString(); String unzippedPath = path.toString();
String[] files = ArrayUtils.removeElement(new File(unzippedPath).list(), ".qortal");
if (files.length == 1) {
// This is a single file resource
inPath = files[0];
}
try { try {
String filename = this.getFilename(unzippedPath, inPath); String filename = this.getFilename(unzippedPath, inPath);
String filePath = Paths.get(unzippedPath, filename).toString(); String filePath = Paths.get(unzippedPath, filename).toString();
@ -174,7 +183,7 @@ public class ArbitraryDataRenderer {
return userPath; return userPath;
} }
private HttpServletResponse getLoadingResponse(Service service, String name, String theme) { private HttpServletResponse getLoadingResponse(Service service, String name, String identifier, String theme) {
String responseString = ""; String responseString = "";
URL url = Resources.getResource("loading/index.html"); URL url = Resources.getResource("loading/index.html");
try { try {
@ -183,6 +192,7 @@ public class ArbitraryDataRenderer {
// Replace vars // Replace vars
responseString = responseString.replace("%%SERVICE%%", service.toString()); responseString = responseString.replace("%%SERVICE%%", service.toString());
responseString = responseString.replace("%%NAME%%", name); responseString = responseString.replace("%%NAME%%", name);
responseString = responseString.replace("%%IDENTIFIER%%", identifier);
responseString = responseString.replace("%%THEME%%", theme); responseString = responseString.replace("%%THEME%%", theme);
} catch (IOException e) { } catch (IOException e) {

3
src/main/resources/loading/index.html

@ -43,8 +43,9 @@
var host = location.protocol + '//' + location.host; var host = location.protocol + '//' + location.host;
var service = "%%SERVICE%%" var service = "%%SERVICE%%"
var name = "%%NAME%%" var name = "%%NAME%%"
var identifier = "%%IDENTIFIER%%"
var url = host + '/arbitrary/resource/status/' + service + '/' + name + '?build=true'; var url = host + '/arbitrary/resource/status/' + service + '/' + name + '/' + identifier + '?build=true';
var textStatus = "Loading..."; var textStatus = "Loading...";
var textProgress = ""; var textProgress = "";
var retryInterval = 2500; var retryInterval = 2500;

17
src/main/resources/q-apps/q-apps.js

@ -81,6 +81,8 @@ window.addEventListener("message", (event) => {
url = "/" + data.name; url = "/" + data.name;
} }
if (data.path != null) url = url.concat((data.path.startsWith("/") ? "" : "/") + data.path); if (data.path != null) url = url.concat((data.path.startsWith("/") ? "" : "/") + data.path);
if (data.identifier != null) url = url.concat("?identifier=" + data.identifier);
window.location = url; window.location = url;
response = true; response = true;
break; break;
@ -228,11 +230,26 @@ function interceptClickEvent(e) {
let parts = href.split("/"); let parts = href.split("/");
const service = parts[0].toUpperCase(); parts.shift(); const service = parts[0].toUpperCase(); parts.shift();
const name = parts[0]; parts.shift(); const name = parts[0]; parts.shift();
let identifier;
if (parts.length > 0) {
identifier = parts[0]; // Do not shift yet
// Check if a resource exists with this service, name and identifier combination
const url = "/arbitrary/resource/status/" + service + "/" + name + "/" + identifier;
const response = httpGet(url);
const responseObj = JSON.parse(response);
if (responseObj.totalChunkCount > 0) {
// Identifier exists, so don't include it in the path
parts.shift();
}
}
const path = parts.join("/"); const path = parts.join("/");
qortalRequest({ qortalRequest({
action: "LINK_TO_QDN_RESOURCE", action: "LINK_TO_QDN_RESOURCE",
service: service, service: service,
name: name, name: name,
identifier: identifier,
path: path path: path
}); });
} }

Loading…
Cancel
Save