mirror of
https://github.com/Qortal/qortal.git
synced 2025-08-02 00:31:25 +00:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
311fe98f44 | ||
|
6f7c8d96b9 | ||
|
ff6ec83b1c | ||
|
ea10eec926 | ||
|
6f724f648d | ||
|
048776e090 | ||
|
a7c02733ec | ||
|
59346db427 | ||
|
25efee55b8 | ||
|
b30445c5f8 | ||
|
d105613e51 | ||
|
ef43e78d54 | ||
|
6f61fbb127 | ||
|
9f9b7cab99 | ||
|
f129e16878 | ||
|
8a42dce763 | ||
|
6423d5e474 | ||
|
6e91157dcf | ||
|
85c61c1bc1 | ||
|
54af36fb85 | ||
|
fcdcc939e6 | ||
|
13450d5afa | ||
|
5e1e653095 | ||
|
e8fabcb449 | ||
|
a4ce41ed39 | ||
|
1b42062d57 | ||
|
c2a4b01a9c | ||
|
47e763b0cf | ||
|
0278f6c9f2 | ||
|
d96bc14516 | ||
|
318f433f22 | ||
|
cfc80cb9b0 | ||
|
01c6149422 | ||
|
6f80a6c08a | ||
|
8fb2d38cd1 | ||
|
5018d27c25 | ||
|
1d77101253 | ||
|
1ddd468c1f | ||
|
f05cd9ea51 | ||
|
70c00a4150 | ||
|
d296029e8e | ||
|
e257fd8628 | ||
|
119c1b43be | ||
|
1277ce38de |
@@ -2,7 +2,7 @@
|
||||
<DOCUMENT Type="Advanced Installer" CreateVersion="14.9" version="18.2" Modules="enterprise" RootPath="." Language="en_GB" Id="{713E21E0-28FC-422F-8A95-823D01A5F80B}">
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
|
||||
<ROW Property="AI_BITMAP_DISPLAY_MODE" Value="0"/>
|
||||
<ROW Property="AI_CURRENT_YEAR" Value="2021" ValueLocId="-"/>
|
||||
<ROW Property="AI_CURRENT_YEAR" Value="2022" ValueLocId="-"/>
|
||||
<ROW Property="AI_FINDEXE_TITLE" Value="Select the installation package for [|ProductName]" ValueLocId="AI.Property.FindExeTitle"/>
|
||||
<ROW Property="AI_PRODUCTNAME_ARP" Value="Qortal"/>
|
||||
<ROW Property="AI_RUN_AS_ADMIN" Value="0"/>
|
||||
@@ -17,10 +17,10 @@
|
||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||
<ROW Property="NTP_GOOD" Value="false"/>
|
||||
<ROW Property="ProductCode" Value="1033:{51EFA0B0-C304-4043-AF6D-2C17C783A998} 1049:{C4662BB2-A247-426E-A128-B7DBD12ECE78} 2052:{1AF44520-C8AB-4261-BCFE-EEA941439718} 2057:{C096EB6A-F43F-45EE-921C-D20F9B993E80} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{1FB9DC61-308D-4726-B993-82B2D51AD453} 1049:{0EAB0862-4F57-4D79-B8B4-C3E1D84484A2} 2052:{28F67872-0D8B-48D1-9DE1-7F11D5919CB8} 2057:{725A8624-CB8A-426D-8162-68980ED45BCB} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="2.1.3" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="3.0.1" Type="32"/>
|
||||
<ROW Property="RECONFIG_NTP" Value="true"/>
|
||||
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
@@ -212,7 +212,7 @@
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{9A243E53-8FC9-4854-B6E2-937493305BB4}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{163A0AFB-3694-4E1B-ABB8-7C5F28F61305}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
|
||||
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
|
||||
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>
|
||||
|
4
pom.xml
4
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>2.1.3</version>
|
||||
<version>3.0.3</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
@@ -23,7 +23,7 @@
|
||||
<hsqldb.version>2.5.1</hsqldb.version>
|
||||
<jersey.version>2.29.1</jersey.version>
|
||||
<jetty.version>9.4.29.v20200521</jetty.version>
|
||||
<log4j.version>2.12.1</log4j.version>
|
||||
<log4j.version>2.17.1</log4j.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<slf4j.version>1.7.12</slf4j.version>
|
||||
<swagger-api.version>2.0.9</swagger-api.version>
|
||||
|
@@ -7,14 +7,13 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.api.ApiKey;
|
||||
import org.qortal.api.ApiRequest;
|
||||
import org.qortal.controller.AutoUpdate;
|
||||
import org.qortal.settings.Settings;
|
||||
@@ -70,14 +69,40 @@ public class ApplyUpdate {
|
||||
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
|
||||
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
|
||||
|
||||
// The /admin/stop endpoint requires an API key, which may or may not be already generated
|
||||
boolean apiKeyNewlyGenerated = false;
|
||||
ApiKey apiKey = null;
|
||||
try {
|
||||
apiKey = new ApiKey();
|
||||
if (!apiKey.generated()) {
|
||||
apiKey.generate();
|
||||
apiKeyNewlyGenerated = true;
|
||||
LOGGER.info("Generated API key");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error loading API key: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Create GET params
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (apiKey != null) {
|
||||
params.put("apiKey", apiKey.toString());
|
||||
}
|
||||
|
||||
// Attempt to stop the node
|
||||
int attempt;
|
||||
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||
final int attemptForLogging = attempt;
|
||||
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
|
||||
String response = ApiRequest.perform(baseUri + "admin/stop", null);
|
||||
if (response == null)
|
||||
String response = ApiRequest.perform(baseUri + "admin/stop", params);
|
||||
if (response == null) {
|
||||
// No response - consider node shut down
|
||||
if (apiKeyNewlyGenerated) {
|
||||
// API key was newly generated for this auto update, so we need to remove it
|
||||
ApplyUpdate.removeGeneratedApiKey();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.info(() -> String.format("Response from API: %s", response));
|
||||
|
||||
@@ -89,6 +114,11 @@ public class ApplyUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
if (apiKeyNewlyGenerated) {
|
||||
// API key was newly generated for this auto update, so we need to remove it
|
||||
ApplyUpdate.removeGeneratedApiKey();
|
||||
}
|
||||
|
||||
if (attempt == MAX_ATTEMPTS) {
|
||||
LOGGER.error("Failed to shutdown node - giving up");
|
||||
return false;
|
||||
@@ -97,6 +127,19 @@ public class ApplyUpdate {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void removeGeneratedApiKey() {
|
||||
try {
|
||||
LOGGER.info("Removing newly generated API key...");
|
||||
|
||||
// Delete the API key since it was only generated for this auto update
|
||||
ApiKey apiKey = new ApiKey();
|
||||
apiKey.delete();
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void replaceJar() {
|
||||
// Assuming current working directory contains the JAR files
|
||||
Path realJar = Paths.get(JAR_FILENAME);
|
||||
|
@@ -81,6 +81,15 @@ public class ApiKey {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
public void delete() throws IOException {
|
||||
this.apiKey = null;
|
||||
|
||||
Path filePath = this.getFilePath();
|
||||
if (Files.exists(filePath)) {
|
||||
Files.delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean generated() {
|
||||
return (this.apiKey != null);
|
||||
|
@@ -19,12 +19,13 @@ public class HTMLParser {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void setDocumentBaseUrl() {
|
||||
public void addAdditionalHeaderTags() {
|
||||
String fileContents = new String(data);
|
||||
Document document = Jsoup.parse(fileContents);
|
||||
String baseUrl = this.linkPrefix + "/";
|
||||
Elements head = document.getElementsByTag("head");
|
||||
if (!head.isEmpty()) {
|
||||
// Add base href tag
|
||||
String baseElement = String.format("<base href=\"%s\">", baseUrl);
|
||||
head.get(0).prepend(baseElement);
|
||||
}
|
||||
|
@@ -121,6 +121,49 @@ public class ArbitraryResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/resources/search")
|
||||
@Operation(
|
||||
summary = "Search arbitrary resources available on chain, optionally filtered by service.\n" +
|
||||
"If default is set to true, only resources without identifiers will be returned.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryResourceInfo.class))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public List<ArbitraryResourceInfo> searchResources(
|
||||
@QueryParam("service") Service service,
|
||||
@QueryParam("query") String query,
|
||||
@Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
|
||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
||||
@Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse,
|
||||
@Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus) {
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
boolean defaultRes = Boolean.TRUE.equals(defaultResource);
|
||||
|
||||
List<ArbitraryResourceInfo> resources = repository.getArbitraryRepository()
|
||||
.searchArbitraryResources(service, query, defaultRes, limit, offset, reverse);
|
||||
|
||||
if (resources == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (includeStatus != null && includeStatus == true) {
|
||||
resources = this.addStatusToResources(resources);
|
||||
}
|
||||
|
||||
return resources;
|
||||
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/resources/names")
|
||||
@Operation(
|
||||
@@ -356,12 +399,14 @@ public class ArbitraryResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public List<ArbitraryTransactionData> getHostedTransactions(@HeaderParam(Security.API_KEY_HEADER) String apiKey) {
|
||||
public List<ArbitraryTransactionData> getHostedTransactions(@HeaderParam(Security.API_KEY_HEADER) String apiKey,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
|
||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<ArbitraryTransactionData> hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository);
|
||||
List<ArbitraryTransactionData> hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository, limit, offset);
|
||||
|
||||
return hostedTransactions;
|
||||
|
||||
@@ -383,28 +428,23 @@ public class ArbitraryResource {
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public List<ArbitraryResourceInfo> getHostedResources(
|
||||
@HeaderParam(Security.API_KEY_HEADER) String apiKey,
|
||||
@Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus) {
|
||||
@Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
|
||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
List<ArbitraryResourceInfo> resources = new ArrayList<>();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
List<ArbitraryTransactionData> transactionDataList = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository);
|
||||
List<ArbitraryTransactionData> transactionDataList = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository, limit, offset);
|
||||
for (ArbitraryTransactionData transactionData : transactionDataList) {
|
||||
ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData);
|
||||
if (transaction.isDataLocal()) {
|
||||
String name = transactionData.getName();
|
||||
Service service = transactionData.getService();
|
||||
String identifier = transactionData.getIdentifier();
|
||||
|
||||
if (transactionData.getName() != null) {
|
||||
List<ArbitraryResourceInfo> transactionResources = repository.getArbitraryRepository()
|
||||
.getArbitraryResources(service, identifier, name, (identifier == null), null, null, false);
|
||||
if (transactionResources != null) {
|
||||
resources.addAll(transactionResources);
|
||||
}
|
||||
}
|
||||
ArbitraryResourceInfo arbitraryResourceInfo = new ArbitraryResourceInfo();
|
||||
arbitraryResourceInfo.name = transactionData.getName();
|
||||
arbitraryResourceInfo.service = transactionData.getService();
|
||||
arbitraryResourceInfo.identifier = transactionData.getIdentifier();
|
||||
if (!resources.contains(arbitraryResourceInfo)) {
|
||||
resources.add(arbitraryResourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,7 +1064,7 @@ public class ArbitraryResource {
|
||||
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
LOGGER.info(String.format("Unable to load %s %s: %s", service, name, e.getMessage()));
|
||||
LOGGER.debug(String.format("Unable to load %s %s: %s", service, name, e.getMessage()));
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FILE_NOT_FOUND, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
@@ -268,9 +268,15 @@ public class BlocksResource {
|
||||
@ApiErrors({
|
||||
ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getLastBlock() {
|
||||
public BlockData getLastBlock(@QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getBlockRepository().getLastBlock();
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
|
||||
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
|
||||
blockData.setOnlineAccountsSignatures(null);
|
||||
}
|
||||
|
||||
return blockData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@@ -548,20 +554,25 @@ public class BlocksResource {
|
||||
@ApiErrors({
|
||||
ApiError.BLOCK_UNKNOWN, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getByTimestamp(@PathParam("timestamp") long timestamp) {
|
||||
public BlockData getByTimestamp(@PathParam("timestamp") long timestamp,
|
||||
@QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = null;
|
||||
|
||||
// Try the Blocks table
|
||||
int height = repository.getBlockRepository().getHeightFromTimestamp(timestamp);
|
||||
if (height > 0) {
|
||||
if (height > 1) {
|
||||
// Found match in Blocks table
|
||||
return repository.getBlockRepository().fromHeight(height);
|
||||
blockData = repository.getBlockRepository().fromHeight(height);
|
||||
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
|
||||
blockData.setOnlineAccountsSignatures(null);
|
||||
}
|
||||
return blockData;
|
||||
}
|
||||
|
||||
// Not found in Blocks table, so try the archive
|
||||
height = repository.getBlockArchiveRepository().getHeightFromTimestamp(timestamp);
|
||||
if (height > 0) {
|
||||
if (height > 1) {
|
||||
// Found match in archive
|
||||
blockData = repository.getBlockArchiveRepository().fromHeight(height);
|
||||
}
|
||||
@@ -571,6 +582,10 @@ public class BlocksResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
||||
}
|
||||
|
||||
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
|
||||
blockData.setOnlineAccountsSignatures(null);
|
||||
}
|
||||
|
||||
return blockData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
|
@@ -391,13 +391,26 @@ public class ArbitraryDataReader {
|
||||
}
|
||||
|
||||
private void decrypt() throws DataException {
|
||||
try {
|
||||
// First try with explicit parameters (CBC mode with PKCS5 padding)
|
||||
this.decryptUsingAlgo("AES/CBC/PKCS5Padding");
|
||||
|
||||
} catch (DataException e) {
|
||||
// Something went wrong, so fall back to default AES params (necessary for legacy resource support)
|
||||
this.decryptUsingAlgo("AES");
|
||||
|
||||
// TODO: delete files and block this resource if privateDataEnabled is false and the second attempt fails too
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptUsingAlgo(String algorithm) throws DataException {
|
||||
// Decrypt if we have the secret key.
|
||||
byte[] secret = this.secret58 != null ? Base58.decode(this.secret58) : null;
|
||||
if (secret != null && secret.length == Transformer.AES256_LENGTH) {
|
||||
try {
|
||||
Path unencryptedPath = Paths.get(this.workingPath.toString(), "zipped.zip");
|
||||
SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, "AES");
|
||||
AES.decryptFile("AES", aesKey, this.filePath.toString(), unencryptedPath.toString());
|
||||
SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, algorithm);
|
||||
AES.decryptFile(algorithm, aesKey, this.filePath.toString(), unencryptedPath.toString());
|
||||
|
||||
// Replace filePath pointer with the encrypted file path
|
||||
// Don't delete the original ArbitraryDataFile, as this is handled in the cleanup phase
|
||||
@@ -405,7 +418,6 @@ public class ArbitraryDataReader {
|
||||
|
||||
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException
|
||||
| BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) {
|
||||
// TODO: delete files and block this resource if privateDataEnabled is false
|
||||
throw new DataException(String.format("Unable to decrypt file at path %s: %s", this.filePath, e.getMessage()));
|
||||
}
|
||||
} else {
|
||||
|
@@ -9,7 +9,6 @@ import org.qortal.arbitrary.ArbitraryDataFile.*;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
@@ -119,7 +118,8 @@ public class ArbitraryDataRenderer {
|
||||
// HTML file - needs to be parsed
|
||||
byte[] data = Files.readAllBytes(Paths.get(filePath)); // TODO: limit file size that can be read into memory
|
||||
HTMLParser htmlParser = new HTMLParser(resourceId, inPath, prefix, usePrefix, data);
|
||||
htmlParser.setDocumentBaseUrl();
|
||||
htmlParser.addAdditionalHeaderTags();
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline'; media-src 'self' blob:");
|
||||
response.setContentType(context.getMimeType(filename));
|
||||
response.setContentLength(htmlParser.getData().length);
|
||||
response.getOutputStream().write(htmlParser.getData());
|
||||
@@ -128,6 +128,7 @@ public class ArbitraryDataRenderer {
|
||||
// Regular file - can be streamed directly
|
||||
File file = new File(filePath);
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline'; media-src 'self' blob:");
|
||||
response.setContentType(context.getMimeType(filename));
|
||||
int bytesRead, length = 0;
|
||||
byte[] buffer = new byte[10240];
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package org.qortal.arbitrary;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
||||
@@ -25,6 +27,8 @@ import static org.qortal.data.arbitrary.ArbitraryResourceStatus.Status;
|
||||
|
||||
public class ArbitraryDataResource {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ArbitraryDataResource.class);
|
||||
|
||||
protected final String resourceId;
|
||||
protected final ResourceIdType resourceIdType;
|
||||
protected final Service service;
|
||||
@@ -83,7 +87,7 @@ public class ArbitraryDataResource {
|
||||
return new ArbitraryResourceStatus(Status.DOWNLOADING);
|
||||
}
|
||||
else if (this.isDataPotentiallyAvailable()) {
|
||||
return new ArbitraryResourceStatus(Status.NOT_STARTED);
|
||||
return new ArbitraryResourceStatus(Status.PUBLISHED);
|
||||
}
|
||||
return new ArbitraryResourceStatus(Status.MISSING_DATA);
|
||||
}
|
||||
@@ -124,7 +128,10 @@ public class ArbitraryDataResource {
|
||||
String identifier = this.identifier != null ? this.identifier : "default";
|
||||
Path cachePath = Paths.get(baseDir, "reader", this.resourceIdType.toString(), this.resourceId, this.service.toString(), identifier);
|
||||
if (cachePath.toFile().exists()) {
|
||||
FilesystemUtils.safeDeleteDirectory(cachePath, true);
|
||||
boolean success = FilesystemUtils.safeDeleteDirectory(cachePath, true);
|
||||
if (success) {
|
||||
LOGGER.info("Cleared cache for resource {}", this.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -225,7 +225,7 @@ public class ArbitraryDataWriter {
|
||||
// Encrypt the file with AES
|
||||
LOGGER.info("Encrypting...");
|
||||
this.aesKey = AES.generateKey(256);
|
||||
AES.encryptFile("AES", this.aesKey, this.filePath.toString(), this.encryptedPath.toString());
|
||||
AES.encryptFile("AES/CBC/PKCS5Padding", this.aesKey, this.filePath.toString(), this.encryptedPath.toString());
|
||||
|
||||
// Delete the input file
|
||||
if (FilesystemUtils.pathInsideDataOrTempPath(this.filePath)) {
|
||||
|
@@ -103,6 +103,18 @@ public class ArbitraryDataFileListManager {
|
||||
}
|
||||
|
||||
long timeSinceLastAttempt = NTP.getTime() - lastAttemptTimestamp;
|
||||
|
||||
// Allow a second attempt after 15 seconds, and another after 30 seconds
|
||||
if (timeSinceLastAttempt > 15 * 1000L) {
|
||||
// We haven't tried for at least 15 seconds
|
||||
|
||||
if (networkBroadcastCount < 3) {
|
||||
// We've made less than 3 total attempts
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then allow another 5 attempts, each 5 minutes apart
|
||||
if (timeSinceLastAttempt > 5 * 60 * 1000L) {
|
||||
// We haven't tried for at least 5 minutes
|
||||
|
||||
@@ -112,6 +124,7 @@ public class ArbitraryDataFileListManager {
|
||||
}
|
||||
}
|
||||
|
||||
// From then on, only try once every 24 hours, to reduce network spam
|
||||
if (timeSinceLastAttempt > 24 * 60 * 60 * 1000L) {
|
||||
// We haven't tried for at least 24 hours
|
||||
return true;
|
||||
@@ -242,7 +255,7 @@ public class ArbitraryDataFileListManager {
|
||||
return ArbitraryDataFileManager.getInstance().fetchDataFilesFromPeersForSignature(signature);
|
||||
}
|
||||
|
||||
LOGGER.debug("Skipping file list request for signature {} due to rate limit", signature58);
|
||||
LOGGER.trace("Skipping file list request for signature {} due to rate limit", signature58);
|
||||
return false;
|
||||
}
|
||||
this.addToSignatureRequests(signature58, true, false);
|
||||
@@ -291,7 +304,20 @@ public class ArbitraryDataFileListManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void deleteFileListRequestsForSignature(byte[] signature) {
|
||||
String signature58 = Base58.encode(signature);
|
||||
for (Iterator<Map.Entry<Integer, Triple<String, Peer, Long>>> it = arbitraryDataFileListRequests.entrySet().iterator(); it.hasNext();) {
|
||||
Map.Entry<Integer, Triple<String, Peer, Long>> entry = it.next();
|
||||
if (entry == null || entry.getKey() == null || entry.getValue() != null) {
|
||||
continue;
|
||||
}
|
||||
if (Objects.equals(entry.getValue().getA(), signature58)) {
|
||||
// Update requests map to reflect that we've received all chunks
|
||||
Triple<String, Peer, Long> newEntry = new Triple<>(null, null, entry.getValue().getC());
|
||||
arbitraryDataFileListRequests.put(entry.getKey(), newEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network handlers
|
||||
|
||||
@@ -304,7 +330,7 @@ public class ArbitraryDataFileListManager {
|
||||
ArbitraryDataFileListMessage arbitraryDataFileListMessage = (ArbitraryDataFileListMessage) message;
|
||||
LOGGER.debug("Received hash list from peer {} with {} hashes", peer, arbitraryDataFileListMessage.getHashes().size());
|
||||
|
||||
// Do we have a pending request for this data? // TODO: might we want to relay all of them anyway?
|
||||
// Do we have a pending request for this data?
|
||||
Triple<String, Peer, Long> request = arbitraryDataFileListRequests.get(message.getId());
|
||||
if (request == null || request.getA() == null) {
|
||||
return;
|
||||
@@ -350,10 +376,6 @@ public class ArbitraryDataFileListManager {
|
||||
// }
|
||||
// }
|
||||
|
||||
// Update requests map to reflect that we've received it
|
||||
Triple<String, Peer, Long> newEntry = new Triple<>(null, null, request.getC());
|
||||
arbitraryDataFileListRequests.put(message.getId(), newEntry);
|
||||
|
||||
if (!isRelayRequest || !Settings.getInstance().isRelayModeEnabled()) {
|
||||
// Go and fetch the actual data, since this isn't a relay request
|
||||
arbitraryDataFileManager.fetchArbitraryDataFiles(repository, peer, signature, arbitraryTransactionData, hashes);
|
||||
@@ -374,8 +396,9 @@ public class ArbitraryDataFileListManager {
|
||||
for (byte[] hash : hashes) {
|
||||
String hash58 = Base58.encode(hash);
|
||||
Triple<String, Peer, Long> value = new Triple<>(signature58, peer, now);
|
||||
arbitraryDataFileManager.arbitraryRelayMap.put(hash58, value);
|
||||
LOGGER.debug("Added {} to relay map: {}, {}, {}", hash58, signature58, peer, now);
|
||||
if (arbitraryDataFileManager.arbitraryRelayMap.putIfAbsent(hash58, value) == null) {
|
||||
LOGGER.debug("Added {} to relay map: {}, {}, {}", hash58, signature58, peer, now);
|
||||
}
|
||||
}
|
||||
|
||||
// Forward to requesting peer
|
||||
@@ -412,6 +435,7 @@ public class ArbitraryDataFileListManager {
|
||||
|
||||
List<byte[]> hashes = new ArrayList<>();
|
||||
ArbitraryTransactionData transactionData = null;
|
||||
boolean allChunksExist = false;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
@@ -430,23 +454,34 @@ public class ArbitraryDataFileListManager {
|
||||
if (metadataHash != null) {
|
||||
arbitraryDataFile.setMetadataHash(metadataHash);
|
||||
|
||||
// Assume all chunks exists, unless one can't be found below
|
||||
allChunksExist = true;
|
||||
|
||||
// If we have the metadata file, add its hash
|
||||
if (arbitraryDataFile.getMetadataFile().exists()) {
|
||||
hashes.add(arbitraryDataFile.getMetadataHash());
|
||||
}
|
||||
else {
|
||||
allChunksExist = false;
|
||||
}
|
||||
|
||||
for (ArbitraryDataFileChunk chunk : arbitraryDataFile.getChunks()) {
|
||||
if (chunk.exists()) {
|
||||
hashes.add(chunk.getHash());
|
||||
//LOGGER.trace("Added hash {}", chunk.getHash58());
|
||||
} else {
|
||||
LOGGER.debug("Couldn't add hash {} because it doesn't exist", chunk.getHash58());
|
||||
LOGGER.trace("Couldn't add hash {} because it doesn't exist", chunk.getHash58());
|
||||
allChunksExist = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This transaction has no chunks, so include the complete file if we have it
|
||||
if (arbitraryDataFile.exists()) {
|
||||
hashes.add(arbitraryDataFile.getHash());
|
||||
allChunksExist = true;
|
||||
}
|
||||
else {
|
||||
allChunksExist = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,49 +494,59 @@ public class ArbitraryDataFileListManager {
|
||||
// We should only respond if we have at least one hash
|
||||
if (hashes.size() > 0) {
|
||||
|
||||
// Update requests map to reflect that we've sent it
|
||||
newEntry = new Triple<>(signature58, null, now);
|
||||
arbitraryDataFileListRequests.put(message.getId(), newEntry);
|
||||
// We have all the chunks, so update requests map to reflect that we've sent it
|
||||
// There is no need to keep track of the request, as we can serve all the chunks
|
||||
if (allChunksExist) {
|
||||
newEntry = new Triple<>(null, null, now);
|
||||
arbitraryDataFileListRequests.put(message.getId(), newEntry);
|
||||
}
|
||||
|
||||
ArbitraryDataFileListMessage arbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature, hashes);
|
||||
arbitraryDataFileListMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(arbitraryDataFileListMessage)) {
|
||||
LOGGER.debug("Couldn't send list of hashes");
|
||||
peer.disconnect("failed to send list of hashes");
|
||||
return;
|
||||
}
|
||||
LOGGER.debug("Sent list of hashes (count: {})", hashes.size());
|
||||
|
||||
if (allChunksExist) {
|
||||
// Nothing left to do, so return to prevent any unnecessary forwarding from occurring
|
||||
LOGGER.debug("No need for any forwarding because file list request is fully served");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
boolean isBlocked = (transactionData == null || ArbitraryDataStorageManager.getInstance().isNameBlocked(transactionData.getName()));
|
||||
if (Settings.getInstance().isRelayModeEnabled() && !isBlocked) {
|
||||
// In relay mode - so ask our other peers if they have it
|
||||
|
||||
long requestTime = getArbitraryDataFileListMessage.getRequestTime();
|
||||
int requestHops = getArbitraryDataFileListMessage.getRequestHops();
|
||||
getArbitraryDataFileListMessage.setRequestHops(++requestHops);
|
||||
long totalRequestTime = now - requestTime;
|
||||
// We may need to forward this request on
|
||||
boolean isBlocked = (transactionData == null || ArbitraryDataStorageManager.getInstance().isNameBlocked(transactionData.getName()));
|
||||
if (Settings.getInstance().isRelayModeEnabled() && !isBlocked) {
|
||||
// In relay mode - so ask our other peers if they have it
|
||||
|
||||
if (totalRequestTime < RELAY_REQUEST_MAX_DURATION) {
|
||||
// Relay request hasn't timed out yet, so can potentially be rebroadcast
|
||||
if (requestHops < RELAY_REQUEST_MAX_HOPS) {
|
||||
// Relay request hasn't reached the maximum number of hops yet, so can be rebroadcast
|
||||
long requestTime = getArbitraryDataFileListMessage.getRequestTime();
|
||||
int requestHops = getArbitraryDataFileListMessage.getRequestHops();
|
||||
getArbitraryDataFileListMessage.setRequestHops(++requestHops);
|
||||
long totalRequestTime = now - requestTime;
|
||||
|
||||
LOGGER.info("Rebroadcasting hash list request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops);
|
||||
Network.getInstance().broadcast(
|
||||
broadcastPeer -> broadcastPeer == peer ||
|
||||
Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost())
|
||||
? null : getArbitraryDataFileListMessage);
|
||||
if (totalRequestTime < RELAY_REQUEST_MAX_DURATION) {
|
||||
// Relay request hasn't timed out yet, so can potentially be rebroadcast
|
||||
if (requestHops < RELAY_REQUEST_MAX_HOPS) {
|
||||
// Relay request hasn't reached the maximum number of hops yet, so can be rebroadcast
|
||||
|
||||
LOGGER.debug("Rebroadcasting hash list request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops);
|
||||
Network.getInstance().broadcast(
|
||||
broadcastPeer -> broadcastPeer == peer ||
|
||||
Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost())
|
||||
? null : getArbitraryDataFileListMessage);
|
||||
|
||||
}
|
||||
else {
|
||||
// This relay request has reached the maximum number of allowed hops
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This relay request has timed out
|
||||
// This relay request has reached the maximum number of allowed hops
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This relay request has timed out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,7 @@ import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.ArbitraryTransactionUtils;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
import org.qortal.utils.Triple;
|
||||
@@ -128,7 +129,7 @@ public class ArbitraryDataFileManager {
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.debug("Already requesting data file {} for signature {}", arbitraryDataFile, Base58.encode(signature));
|
||||
LOGGER.trace("Already requesting data file {} for signature {}", arbitraryDataFile, Base58.encode(signature));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +188,9 @@ public class ArbitraryDataFileManager {
|
||||
arbitraryDataFileRequests.remove(hash58);
|
||||
LOGGER.trace(String.format("Removed hash %.8s from arbitraryDataFileRequests", hash58));
|
||||
|
||||
// We may need to remove the file list request, if we have all the files for this transaction
|
||||
this.handleFileListRequests(signature);
|
||||
|
||||
if (message == null || message.getType() != Message.MessageType.ARBITRARY_DATA_FILE) {
|
||||
return null;
|
||||
}
|
||||
@@ -202,13 +206,44 @@ public class ArbitraryDataFileManager {
|
||||
// File didn't exist locally before the request, and it's a forwarding request, so delete it
|
||||
LOGGER.debug("Deleting file {} because it was needed for forwarding only", Base58.encode(hash));
|
||||
ArbitraryDataFile dataFile = arbitraryDataFileMessage.getArbitraryDataFile();
|
||||
dataFile.delete();
|
||||
|
||||
// Keep trying to delete the data until it is deleted, or we reach 10 attempts
|
||||
for (int i=0; i<10; i++) {
|
||||
if (dataFile.delete()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException e) {
|
||||
// Fall through to exit method
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arbitraryDataFileMessage;
|
||||
}
|
||||
|
||||
private void handleFileListRequests(byte[] signature) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Fetch the transaction data
|
||||
ArbitraryTransactionData arbitraryTransactionData = ArbitraryTransactionUtils.fetchTransactionData(repository, signature);
|
||||
if (arbitraryTransactionData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allChunksExist = ArbitraryTransactionUtils.allChunksExist(arbitraryTransactionData);
|
||||
|
||||
if (allChunksExist) {
|
||||
// Update requests map to reflect that we've received all chunks
|
||||
ArbitraryDataFileListManager.getInstance().deleteFileListRequestsForSignature(signature);
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.debug("Unable to handle file list requests: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void handleArbitraryDataFileForwarding(Peer requestingPeer, Message message, Message originalMessage) {
|
||||
// Return if there is no originally requesting peer to forward to
|
||||
|
@@ -338,7 +338,7 @@ public class ArbitraryDataManager extends Thread {
|
||||
ArbitraryDataResource resource =
|
||||
new ArbitraryDataResource(resourceId, ArbitraryDataFile.ResourceIdType.NAME, service, identifier);
|
||||
String key = resource.getUniqueKey();
|
||||
LOGGER.info("Clearing cache for {}...", resource);
|
||||
LOGGER.trace("Clearing cache for {}...", resource);
|
||||
|
||||
if (this.arbitraryDataCachedResources.containsKey(key)) {
|
||||
this.arbitraryDataCachedResources.remove(key);
|
||||
@@ -367,7 +367,7 @@ public class ArbitraryDataManager extends Thread {
|
||||
|
||||
public void broadcastHostedSignatureList() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<ArbitraryTransactionData> hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository);
|
||||
List<ArbitraryTransactionData> hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository, null, null);
|
||||
List<byte[]> hostedSignatures = hostedTransactions.stream().map(ArbitraryTransactionData::getSignature).collect(Collectors.toList());
|
||||
if (!hostedSignatures.isEmpty()) {
|
||||
// Broadcast the list, using null to represent our peer address
|
||||
|
@@ -10,6 +10,7 @@ import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.ArbitraryTransactionUtils;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.FilesystemUtils;
|
||||
import org.qortal.utils.NTP;
|
||||
@@ -19,6 +20,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -257,10 +259,10 @@ public class ArbitraryDataStorageManager extends Thread {
|
||||
|
||||
// Hosted data
|
||||
|
||||
public List<ArbitraryTransactionData> listAllHostedTransactions(Repository repository) {
|
||||
public List<ArbitraryTransactionData> listAllHostedTransactions(Repository repository, Integer limit, Integer offset) {
|
||||
// Load from cache if we can, to avoid disk reads
|
||||
if (this.hostedTransactions != null) {
|
||||
return this.hostedTransactions;
|
||||
return ArbitraryTransactionUtils.limitOffsetTransactions(this.hostedTransactions, limit, offset);
|
||||
}
|
||||
|
||||
List<ArbitraryTransactionData> arbitraryTransactionDataList = new ArrayList<>();
|
||||
@@ -290,10 +292,13 @@ public class ArbitraryDataStorageManager extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by newest first
|
||||
arbitraryTransactionDataList.sort(Comparator.comparingLong(ArbitraryTransactionData::getTimestamp).reversed());
|
||||
|
||||
// Update cache
|
||||
this.hostedTransactions = arbitraryTransactionDataList;
|
||||
|
||||
return arbitraryTransactionDataList;
|
||||
return ArbitraryTransactionUtils.limitOffsetTransactions(arbitraryTransactionDataList, limit, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,7 +451,7 @@ public class ArbitraryDataStorageManager extends Thread {
|
||||
long maxStoragePerName = this.storageCapacityPerName(threshold);
|
||||
|
||||
// Fetch all hosted transactions
|
||||
List<ArbitraryTransactionData> hostedTransactions = this.listAllHostedTransactions(repository);
|
||||
List<ArbitraryTransactionData> hostedTransactions = this.listAllHostedTransactions(repository, null, null);
|
||||
for (ArbitraryTransactionData transactionData : hostedTransactions) {
|
||||
String transactionName = transactionData.getName();
|
||||
if (!Objects.equals(name, transactionName)) {
|
||||
|
@@ -4,6 +4,7 @@ import org.qortal.arbitrary.misc.Service;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.Objects;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class ArbitraryResourceInfo {
|
||||
@@ -13,7 +14,24 @@ public class ArbitraryResourceInfo {
|
||||
public String identifier;
|
||||
public ArbitraryResourceStatus status;
|
||||
|
||||
public Long size;
|
||||
|
||||
public ArbitraryResourceInfo() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
|
||||
if (!(o instanceof ArbitraryResourceInfo))
|
||||
return false;
|
||||
|
||||
ArbitraryResourceInfo other = (ArbitraryResourceInfo) o;
|
||||
|
||||
return Objects.equals(this.name, other.name) &&
|
||||
Objects.equals(this.service, other.service) &&
|
||||
Objects.equals(this.identifier, other.identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
||||
public class ArbitraryResourceStatus {
|
||||
|
||||
public enum Status {
|
||||
NOT_STARTED("Not started", "Downloading not yet started"),
|
||||
PUBLISHED("Published", "Published but not yet downloaded"),
|
||||
DOWNLOADING("Downloading", "Locating and downloading files..."),
|
||||
DOWNLOADED("Downloaded", "Files downloaded"),
|
||||
BUILDING("Building", "Building..."),
|
||||
|
@@ -1137,10 +1137,14 @@ public class Network {
|
||||
this.ourExternalIpAddressHistory.remove(0);
|
||||
}
|
||||
|
||||
// Now take a copy of the IP address history so it can be safely iterated
|
||||
// Without this, another thread could remove an element, resulting in an exception
|
||||
List<String> ipAddressHistory = new ArrayList<>(this.ourExternalIpAddressHistory);
|
||||
|
||||
// If we've had 10 consecutive matching addresses, and they're different from
|
||||
// our stored IP address value, treat it as updated.
|
||||
int consecutiveReadingsRequired = 10;
|
||||
int size = this.ourExternalIpAddressHistory.size();
|
||||
int size = ipAddressHistory.size();
|
||||
if (size < consecutiveReadingsRequired) {
|
||||
// Need at least 10 readings
|
||||
return;
|
||||
@@ -1150,7 +1154,7 @@ public class Network {
|
||||
String lastReading = null;
|
||||
int consecutiveReadings = 0;
|
||||
for (int i = size-1; i >= 0; i--) {
|
||||
String reading = this.ourExternalIpAddressHistory.get(i);
|
||||
String reading = ipAddressHistory.get(i);
|
||||
if (lastReading != null) {
|
||||
if (Objects.equals(reading, lastReading)) {
|
||||
consecutiveReadings++;
|
||||
@@ -1164,7 +1168,7 @@ public class Network {
|
||||
|
||||
if (consecutiveReadings >= consecutiveReadingsRequired) {
|
||||
// Last 10 readings were the same - i.e. more than one peer agreed on the new IP address...
|
||||
String ip = this.ourExternalIpAddressHistory.get(size - 1);
|
||||
String ip = ipAddressHistory.get(size - 1);
|
||||
if (!Objects.equals(ip, this.ourExternalIpAddress)) {
|
||||
// ... and the readings were different to our current recorded value, so
|
||||
// update our external IP address value
|
||||
|
@@ -473,16 +473,18 @@ public class Peer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
byte[] leadingBytes = new byte[Math.min(bytesRead, 8)];
|
||||
this.byteBuffer.asReadOnlyBuffer().position(priorPosition).get(leadingBytes);
|
||||
String leadingHex = HashCode.fromBytes(leadingBytes).toString();
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
if (bytesRead > 0) {
|
||||
byte[] leadingBytes = new byte[Math.min(bytesRead, 8)];
|
||||
this.byteBuffer.asReadOnlyBuffer().position(priorPosition).get(leadingBytes);
|
||||
String leadingHex = HashCode.fromBytes(leadingBytes).toString();
|
||||
|
||||
LOGGER.trace("[{}] Received {} bytes, starting {}, into byteBuffer[{}] from peer {}",
|
||||
this.peerConnectionId, bytesRead, leadingHex, priorPosition, this);
|
||||
} else {
|
||||
LOGGER.trace("[{}] Received {} bytes into byteBuffer[{}] from peer {}", this.peerConnectionId,
|
||||
bytesRead, priorPosition, this);
|
||||
LOGGER.trace("[{}] Received {} bytes, starting {}, into byteBuffer[{}] from peer {}",
|
||||
this.peerConnectionId, bytesRead, leadingHex, priorPosition, this);
|
||||
} else {
|
||||
LOGGER.trace("[{}] Received {} bytes into byteBuffer[{}] from peer {}", this.peerConnectionId,
|
||||
bytesRead, priorPosition, this);
|
||||
}
|
||||
}
|
||||
final boolean wasByteBufferFull = !this.byteBuffer.hasRemaining();
|
||||
|
||||
|
@@ -15,7 +15,7 @@ import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
public class OnlineAccountsMessage extends Message {
|
||||
private static final int MAX_ACCOUNT_COUNT = 1000;
|
||||
private static final int MAX_ACCOUNT_COUNT = 5000;
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
|
@@ -26,6 +26,8 @@ public interface ArbitraryRepository {
|
||||
|
||||
public List<ArbitraryResourceInfo> getArbitraryResources(Service service, String identifier, String name, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
public List<ArbitraryResourceInfo> searchArbitraryResources(Service service, String query, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
public List<ArbitraryResourceNameInfo> getArbitraryResourceCreatorNames(Service service, String identifier, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package org.qortal.repository.hsqldb;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.util.Longs;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.data.arbitrary.ArbitraryResourceInfo;
|
||||
import org.qortal.crypto.Crypto;
|
||||
@@ -305,7 +306,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
sql.append("SELECT name, service, identifier FROM ArbitraryTransactions WHERE 1=1");
|
||||
sql.append("SELECT name, service, identifier, MAX(size) AS max_size FROM ArbitraryTransactions WHERE 1=1");
|
||||
|
||||
if (service != null) {
|
||||
sql.append(" AND service = ");
|
||||
@@ -347,6 +348,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
||||
String nameResult = resultSet.getString(1);
|
||||
Service serviceResult = Service.valueOf(resultSet.getInt(2));
|
||||
String identifierResult = resultSet.getString(3);
|
||||
Integer sizeResult = resultSet.getInt(4);
|
||||
|
||||
// We should filter out resources without names
|
||||
if (nameResult == null) {
|
||||
@@ -357,6 +359,78 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
||||
arbitraryResourceInfo.name = nameResult;
|
||||
arbitraryResourceInfo.service = serviceResult;
|
||||
arbitraryResourceInfo.identifier = identifierResult;
|
||||
arbitraryResourceInfo.size = Longs.valueOf(sizeResult);
|
||||
|
||||
arbitraryResources.add(arbitraryResourceInfo);
|
||||
} while (resultSet.next());
|
||||
|
||||
return arbitraryResources;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch arbitrary transactions from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArbitraryResourceInfo> searchArbitraryResources(Service service, String query,
|
||||
boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
// For now we are searching anywhere in the fields
|
||||
// Note that this will bypass any indexes so may not scale well
|
||||
// Longer term we probably want to copy resources to their own table anyway
|
||||
String queryWildcard = String.format("%%%s%%", query.toLowerCase());
|
||||
|
||||
sql.append("SELECT name, service, identifier, MAX(size) AS max_size FROM ArbitraryTransactions WHERE 1=1");
|
||||
|
||||
if (service != null) {
|
||||
sql.append(" AND service = ");
|
||||
sql.append(service.value);
|
||||
}
|
||||
|
||||
if (defaultResource) {
|
||||
// Default resource requested - use NULL identifier and search name only
|
||||
sql.append(" AND LCASE(name) LIKE ? AND identifier IS NULL");
|
||||
bindParams.add(queryWildcard);
|
||||
}
|
||||
else {
|
||||
// Non-default resource requested
|
||||
// In this case we search the identifier as well as the name
|
||||
sql.append(" AND (LCASE(name) LIKE ? OR LCASE(identifier) LIKE ?)");
|
||||
bindParams.add(queryWildcard);
|
||||
bindParams.add(queryWildcard);
|
||||
}
|
||||
|
||||
sql.append(" GROUP BY name, service, identifier ORDER BY name");
|
||||
|
||||
if (reverse != null && reverse) {
|
||||
sql.append(" DESC");
|
||||
}
|
||||
|
||||
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
|
||||
|
||||
List<ArbitraryResourceInfo> arbitraryResources = new ArrayList<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
do {
|
||||
String nameResult = resultSet.getString(1);
|
||||
Service serviceResult = Service.valueOf(resultSet.getInt(2));
|
||||
String identifierResult = resultSet.getString(3);
|
||||
Integer sizeResult = resultSet.getInt(4);
|
||||
|
||||
// We should filter out resources without names
|
||||
if (nameResult == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArbitraryResourceInfo arbitraryResourceInfo = new ArbitraryResourceInfo();
|
||||
arbitraryResourceInfo.name = nameResult;
|
||||
arbitraryResourceInfo.service = serviceResult;
|
||||
arbitraryResourceInfo.identifier = identifierResult;
|
||||
arbitraryResourceInfo.size = Longs.valueOf(sizeResult);
|
||||
|
||||
arbitraryResources.add(arbitraryResourceInfo);
|
||||
} while (resultSet.next());
|
||||
|
@@ -59,6 +59,10 @@ public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepos
|
||||
if (arbitraryTransactionData.getVersion() >= 4)
|
||||
this.repository.getArbitraryRepository().save(arbitraryTransactionData);
|
||||
|
||||
// method and compression use NOT NULL DEFAULT 0, so fall back to these values if null
|
||||
Integer method = arbitraryTransactionData.getMethod() != null ? arbitraryTransactionData.getMethod().value : 0;
|
||||
Integer compression = arbitraryTransactionData.getCompression() != null ? arbitraryTransactionData.getCompression().value : 0;
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("ArbitraryTransactions");
|
||||
|
||||
saveHelper.bind("signature", arbitraryTransactionData.getSignature()).bind("sender", arbitraryTransactionData.getSenderPublicKey())
|
||||
@@ -66,8 +70,8 @@ public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepos
|
||||
.bind("nonce", arbitraryTransactionData.getNonce()).bind("size", arbitraryTransactionData.getSize())
|
||||
.bind("is_data_raw", arbitraryTransactionData.getDataType() == DataType.RAW_DATA).bind("data", arbitraryTransactionData.getData())
|
||||
.bind("metadata_hash", arbitraryTransactionData.getMetadataHash()).bind("name", arbitraryTransactionData.getName())
|
||||
.bind("identifier", arbitraryTransactionData.getIdentifier()).bind("update_method", arbitraryTransactionData.getMethod().value)
|
||||
.bind("secret", arbitraryTransactionData.getSecret()).bind("compression", arbitraryTransactionData.getCompression().value);
|
||||
.bind("identifier", arbitraryTransactionData.getIdentifier()).bind("update_method", method)
|
||||
.bind("secret", arbitraryTransactionData.getSecret()).bind("compression", compression);
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
|
@@ -195,7 +195,7 @@ public class Settings {
|
||||
private int maxRetries = 2;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "1.5.0";
|
||||
private String minPeerVersion = "3.0.1";
|
||||
/** Whether to allow connections with peers below minPeerVersion
|
||||
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||
|
@@ -17,7 +17,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
@@ -352,4 +355,25 @@ public class ArbitraryTransactionUtils {
|
||||
return filesRelocatedCount;
|
||||
}
|
||||
|
||||
public static List<ArbitraryTransactionData> limitOffsetTransactions(List<ArbitraryTransactionData> transactions,
|
||||
Integer limit, Integer offset) {
|
||||
if (limit != null && limit == 0) {
|
||||
limit = null;
|
||||
}
|
||||
if (limit == null && offset == null) {
|
||||
return transactions;
|
||||
}
|
||||
if (offset == null) {
|
||||
offset = 0;
|
||||
}
|
||||
if (offset > transactions.size() - 1) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (limit == null) {
|
||||
return transactions.stream().skip(offset).collect(Collectors.toList());
|
||||
}
|
||||
return transactions.stream().skip(offset).limit(limit).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -150,17 +150,24 @@ public class FilesystemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void safeDeleteDirectory(Path path, boolean cleanup) throws IOException {
|
||||
public static boolean safeDeleteDirectory(Path path, boolean cleanup) throws IOException {
|
||||
boolean success = false;
|
||||
|
||||
// Delete path, if it exists in our data/temp directory
|
||||
if (FilesystemUtils.pathInsideDataOrTempPath(path)) {
|
||||
File directory = new File(path.toString());
|
||||
FileUtils.deleteDirectory(directory);
|
||||
if (Files.exists(path)) {
|
||||
File directory = new File(path.toString());
|
||||
FileUtils.deleteDirectory(directory);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanup) {
|
||||
if (success && cleanup) {
|
||||
// Delete the parent directories if they are empty (and exist in our data/temp directory)
|
||||
FilesystemUtils.safeDeleteEmptyParentDirectories(path);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public static void safeDeleteEmptyParentDirectories(Path path) throws IOException {
|
||||
|
@@ -2,10 +2,12 @@ package org.qortal.test.api;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.api.resource.AdminResource;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.test.common.ApiCommon;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
@@ -29,7 +31,10 @@ public class AdminApiTests extends ApiCommon {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSummary() {
|
||||
public void testSummary() throws IllegalAccessException {
|
||||
// Set localAuthBypassEnabled to true, since we don't need to test authentication here
|
||||
FieldUtils.writeField(Settings.getInstance(), "localAuthBypassEnabled", true, true);
|
||||
|
||||
assertNotNull(this.adminResource.summary("testApiKey"));
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.api.ApiError;
|
||||
@@ -76,8 +77,9 @@ public class BlockApiTests extends ApiCommon {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBlockByTimestamp() {
|
||||
assertNotNull(this.blocksResource.getByTimestamp(System.currentTimeMillis()));
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testGetBlockByTimestamp() throws DataException {
|
||||
assertNotNull(this.blocksResource.getByTimestamp(System.currentTimeMillis(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -8,6 +8,7 @@ import org.qortal.test.common.Common;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
@@ -38,7 +39,9 @@ public class ArbitraryDataDigestTests extends Common {
|
||||
// Write a random file to .qortal/cache to ensure it isn't being included in the digest function
|
||||
// We exclude all .qortal files from the digest since they can be different with each build, and
|
||||
// we only care about the actual user files
|
||||
FileWriter fileWriter = new FileWriter(Paths.get(dataPath.toString(), ".qortal", "cache").toString());
|
||||
Path cachePath = Paths.get(dataPath.toString(), ".qortal", "cache");
|
||||
Files.createDirectories(cachePath.getParent());
|
||||
FileWriter fileWriter = new FileWriter(cachePath.toString());
|
||||
fileWriter.append(UUID.randomUUID().toString());
|
||||
fileWriter.close();
|
||||
|
||||
|
@@ -15,6 +15,5 @@
|
||||
"tempDataPath": "data-test/_temp",
|
||||
"listsPath": "lists-test",
|
||||
"storagePolicy": "FOLLOWED_OR_VIEWED",
|
||||
"maxStorageCapacity": 104857600,
|
||||
"localAuthBypassEnabled": true
|
||||
"maxStorageCapacity": 104857600
|
||||
}
|
||||
|
53
stop.sh
53
stop.sh
@@ -21,15 +21,50 @@ fi
|
||||
read pid 2>/dev/null <run.pid
|
||||
is_pid_valid=$?
|
||||
|
||||
if [ -z "${pid}" ]; then
|
||||
# Attempt to locate the process ID
|
||||
pid=$(ps aux | grep '[q]ortal.jar' | head -n 1 | awk '{print $2}')
|
||||
# Swap out the API port if the --testnet (or -t) argument is specified
|
||||
api_port=12391
|
||||
if [[ "$@" = *"--testnet"* ]] || [[ "$@" = *"-t"* ]]; then
|
||||
api_port=62391
|
||||
fi
|
||||
|
||||
echo "Stopping Qortal process $pid..."
|
||||
if kill "${pid}"; then
|
||||
echo "Qortal node should be shutting down"
|
||||
# Attempt to locate the process ID if we don't have one
|
||||
if [ -z "${pid}" ]; then
|
||||
pid=$(ps aux | grep '[q]ortal.jar' | head -n 1 | awk '{print $2}')
|
||||
is_pid_valid=$?
|
||||
fi
|
||||
|
||||
# Locate the API key if it exists
|
||||
apikey=$(cat apikey.txt)
|
||||
success=0
|
||||
|
||||
# Try and stop via the API
|
||||
if [ -n "$apikey" ]; then
|
||||
echo "Stopping Qortal via API..."
|
||||
if curl --url "http://localhost:${api_port}/admin/stop?apiKey=$apikey" 1>/dev/null 2>&1; then
|
||||
success=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try to kill process with SIGTERM
|
||||
if [ "$success" -ne 1 ] && [ -n "$pid" ]; then
|
||||
echo "Stopping Qortal process $pid..."
|
||||
if kill -15 "${pid}"; then
|
||||
success=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Warn and exit if still no success
|
||||
if [ "$success" -ne 1 ]; then
|
||||
if [ -n "$pid" ]; then
|
||||
echo "${red}Stop command failed - not running with process id ${pid}?${normal}"
|
||||
else
|
||||
echo "${red}Stop command failed - not running?${normal}"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$success" -eq 1 ]; then
|
||||
echo "Qortal node should be shutting down"
|
||||
if [ "${is_pid_valid}" -eq 0 ]; then
|
||||
echo -n "Monitoring for Qortal node to end"
|
||||
while s=`ps -p $pid -o stat=` && [[ "$s" && "$s" != 'Z' ]]; do
|
||||
@@ -40,8 +75,6 @@ if kill "${pid}"; then
|
||||
echo "${green}Qortal ended gracefully${normal}"
|
||||
rm -f run.pid
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
echo "${red}Stop command failed - not running with process id ${pid}?${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
@@ -7,7 +7,7 @@ fi
|
||||
|
||||
printf "Searching for auto-update transactions to approve...\n";
|
||||
|
||||
tx=$( curl --silent --url "http://localhost:${port}/arbitrary/search?txGroupId=1&service=1&confirmationStatus=CONFIRMED&limit=1&reverse=true" );
|
||||
tx=$( curl --silent --url "http://localhost:${port}/arbitrary/search?txGroupId=1&service=AUTO_UPDATE&confirmationStatus=CONFIRMED&limit=1&reverse=true" );
|
||||
if fgrep --silent '"approvalStatus":"PENDING"' <<< "${tx}"; then
|
||||
true
|
||||
else
|
||||
|
18
tools/qdata
18
tools/qdata
@@ -22,6 +22,16 @@ if [ -z "$*" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
if [ -f "apikey.txt" ]; then
|
||||
apikey=$(cat "apikey.txt")
|
||||
elif [ -f "${script_dir}/../apikey.txt" ]; then
|
||||
apikey=$(cat "${script_dir}/../apikey.txt")
|
||||
elif [ -f "${HOME}/qortal/apikey.txt" ]; then
|
||||
apikey=$(cat "${HOME}/qortal/apikey.txt")
|
||||
fi
|
||||
|
||||
method=$1
|
||||
service=$2
|
||||
name=$3
|
||||
@@ -67,7 +77,7 @@ if [[ "${method}" == "POST" ]]; then
|
||||
fi
|
||||
|
||||
echo "Creating transaction - this can take a while..."
|
||||
tx_data=$(curl --silent --insecure -X ${method} "http://${host}:${port}/arbitrary/${service}/${name}/${identifier}${type_component}" -d "${data}")
|
||||
tx_data=$(curl --silent --insecure -X ${method} "http://${host}:${port}/arbitrary/${service}/${name}/${identifier}${type_component}" -H "X-API-KEY: ${apikey}" -d "${data}")
|
||||
|
||||
if [[ "${tx_data}" == *"error"* || "${tx_data}" == *"ERROR"* ]]; then
|
||||
echo "${tx_data}"; exit
|
||||
@@ -76,7 +86,7 @@ if [[ "${method}" == "POST" ]]; then
|
||||
fi
|
||||
|
||||
echo "Computing nonce..."
|
||||
computed_tx_data=$(curl --silent --insecure -X POST "http://${host}:${port}/arbitrary/compute" -H "Content-Type: application/json" -d "${tx_data}")
|
||||
computed_tx_data=$(curl --silent --insecure -X POST "http://${host}:${port}/arbitrary/compute" -H "Content-Type: application/json" -H "X-API-KEY: ${apikey}" -d "${tx_data}")
|
||||
if [[ "${computed_tx_data}" == *"error"* || "${computed_tx_data}" == *"ERROR"* ]]; then
|
||||
echo "${computed_tx_data}"; exit
|
||||
fi
|
||||
@@ -112,9 +122,9 @@ elif [[ "${method}" == "GET" ]]; then
|
||||
|
||||
# We use a different API depending on whether or not an identifier is supplied
|
||||
if [ -n "${identifier}" ]; then
|
||||
response=$(curl --silent --insecure -X GET "http://${host}:${port}/arbitrary/${service}/${name}/${identifier}?rebuild=${rebuild}&filepath=${filepath}")
|
||||
response=$(curl --silent --insecure -X GET "http://${host}:${port}/arbitrary/${service}/${name}/${identifier}?rebuild=${rebuild}&filepath=${filepath}" -H "X-API-KEY: ${apikey}")
|
||||
else
|
||||
response=$(curl --silent --insecure -X GET "http://${host}:${port}/arbitrary/${service}/${name}?rebuild=${rebuild}&filepath=${filepath}")
|
||||
response=$(curl --silent --insecure -X GET "http://${host}:${port}/arbitrary/${service}/${name}?rebuild=${rebuild}&filepath=${filepath}" -H "X-API-KEY: ${apikey}")
|
||||
fi
|
||||
|
||||
if [ -z "${response}" ]; then
|
||||
|
12
tools/qort
12
tools/qort
@@ -10,6 +10,8 @@ example_host=node10.qortal.org
|
||||
# called-as name
|
||||
name="${0##*/}"
|
||||
|
||||
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
while [ -n "$*" ]; do
|
||||
case $1 in
|
||||
-p)
|
||||
@@ -65,6 +67,14 @@ fi
|
||||
url=$1
|
||||
shift
|
||||
|
||||
if [ -f "apikey.txt" ]; then
|
||||
apikey=$(cat "apikey.txt")
|
||||
elif [ -f "${script_dir}/../apikey.txt" ]; then
|
||||
apikey=$(cat "${script_dir}/../apikey.txt")
|
||||
elif [ -f "${HOME}/qortal/apikey.txt" ]; then
|
||||
apikey=$(cat "${HOME}/qortal/apikey.txt")
|
||||
fi
|
||||
|
||||
if [ "${url:0:4}" != "http" ]; then
|
||||
base_url=${BASE_URL-localhost:${port}}
|
||||
|
||||
@@ -83,5 +93,5 @@ if [ "$#" != 0 ]; then
|
||||
data="--data"
|
||||
fi
|
||||
|
||||
curl --silent --insecure --connect-timeout 5 ${content_type:+--header} "${content_type}" ${method} ${src} --url ${url} ${data} "$@" | ${postproc}
|
||||
curl --silent --insecure --connect-timeout 5 -H "X-API-KEY: ${apikey}" ${content_type:+--header} "${content_type}" ${method} ${src} --url ${url} ${data} "$@" | ${postproc}
|
||||
echo
|
||||
|
Reference in New Issue
Block a user