cleaning structure
This commit is contained in:
133
old code/tray/src/qz/App.java
Executable file
133
old code/tray/src/qz/App.java
Executable file
@@ -0,0 +1,133 @@
|
||||
package qz;
|
||||
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.Filter;
|
||||
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
||||
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
|
||||
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
|
||||
import org.apache.logging.log4j.core.filter.ThresholdFilter;
|
||||
import org.apache.logging.log4j.core.layout.PatternLayout;
|
||||
import qz.build.provision.params.Phase;
|
||||
import qz.common.Constants;
|
||||
import qz.installer.Installer;
|
||||
import qz.installer.certificate.CertificateManager;
|
||||
import qz.installer.certificate.ExpiryTask;
|
||||
import qz.installer.certificate.KeyPairWrapper;
|
||||
import qz.installer.certificate.NativeCertificateInstaller;
|
||||
import qz.installer.provision.ProvisionInstaller;
|
||||
import qz.ui.PairingConfigDialog;
|
||||
import qz.utils.*;
|
||||
import qz.ws.PrintSocketServer;
|
||||
import qz.ws.SingleInstanceChecker;
|
||||
import qz.ws.substitutions.Substitutions;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Properties;
|
||||
|
||||
public class App {
|
||||
private static final Logger log = LogManager.getLogger(App.class);
|
||||
private static Properties trayProperties = null;
|
||||
|
||||
public static void main(String ... args) {
|
||||
ArgParser parser = new ArgParser(args);
|
||||
LibUtilities.getInstance().bind();
|
||||
if(parser.intercept()) {
|
||||
FileUtilities.cleanup();
|
||||
System.exit(parser.getExitCode());
|
||||
}
|
||||
SingleInstanceChecker.stealWebsocket = parser.hasFlag(ArgValue.STEAL);
|
||||
setupFileLogging();
|
||||
log.info(Constants.ABOUT_TITLE + " version: {}", Constants.VERSION);
|
||||
log.info(Constants.ABOUT_TITLE + " vendor: {}", Constants.ABOUT_COMPANY);
|
||||
log.info("Java version: {}", Constants.JAVA_VERSION.toString());
|
||||
log.info("Java vendor: {}", Constants.JAVA_VENDOR);
|
||||
Substitutions.setEnabled(PrefsSearch.getBoolean(ArgValue.SECURITY_SUBSTITUTIONS_ENABLE));
|
||||
Substitutions.setStrict(PrefsSearch.getBoolean(ArgValue.SECURITY_SUBSTITUTIONS_STRICT));
|
||||
|
||||
CertificateManager certManager = null;
|
||||
try {
|
||||
// Gets and sets the SSL info, properties file
|
||||
certManager = Installer.getInstance().certGen(false);
|
||||
trayProperties = certManager.getProperties();
|
||||
// Reoccurring (e.g. hourly) cert expiration check
|
||||
new ExpiryTask(certManager).schedule();
|
||||
} catch(Exception e) {
|
||||
log.error("Something went critically wrong loading HTTPS", e);
|
||||
}
|
||||
Installer.getInstance().addUserSettings();
|
||||
|
||||
// Load overridable preferences set in qz-tray.properties file
|
||||
NetworkUtilities.setPreferences(certManager.getProperties());
|
||||
SingleInstanceChecker.setPreferences(certManager.getProperties());
|
||||
|
||||
// Linux needs the cert installed in user-space on every launch for Chrome SSL to work
|
||||
if(!SystemUtilities.isWindows() && !SystemUtilities.isMac()) {
|
||||
X509Certificate caCert = certManager.getKeyPair(KeyPairWrapper.Type.CA).getCert();
|
||||
// Only install if a CA cert exists (e.g. one we generated)
|
||||
if(caCert != null) {
|
||||
NativeCertificateInstaller.getInstance().install(certManager.getKeyPair(KeyPairWrapper.Type.CA).getCert());
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke any provisioning steps that are phase=startup
|
||||
try {
|
||||
ProvisionInstaller provisionInstaller = new ProvisionInstaller(SystemUtilities.getJarParentPath().resolve(Constants.PROVISION_DIR));
|
||||
provisionInstaller.invoke(Phase.STARTUP);
|
||||
} catch(Exception e) {
|
||||
log.warn("An error occurred provisioning \"phase\": \"startup\" entries", e);
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Starting {} {}", Constants.ABOUT_TITLE, Constants.VERSION);
|
||||
// Start the WebSocket
|
||||
PrintSocketServer.runServer(certManager, parser.isHeadless());
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.error("Could not start tray manager", e);
|
||||
}
|
||||
FileUtilities.cleanup();
|
||||
log.warn("The web socket server is no longer running");
|
||||
|
||||
// Show pairing config dialog if needed
|
||||
PairingConfigDialog.showIfNeeded(null); // null for no parent frame, or pass your main frame if available
|
||||
}
|
||||
|
||||
public static Properties getTrayProperties() {
|
||||
return trayProperties;
|
||||
}
|
||||
|
||||
private static void setupFileLogging() {
|
||||
//disable jetty logging
|
||||
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
|
||||
System.setProperty("org.eclipse.jetty.LEVEL", "OFF");
|
||||
|
||||
if(PrefsSearch.getBoolean(ArgValue.LOG_DISABLE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int logSize = PrefsSearch.getInt(ArgValue.LOG_SIZE);
|
||||
int logRotate = PrefsSearch.getInt(ArgValue.LOG_ROTATE);
|
||||
Installer.getInstance().cleanupLegacyLogs(Math.max(logRotate, 5));
|
||||
RollingFileAppender fileAppender = RollingFileAppender.newBuilder()
|
||||
.setName("log-file")
|
||||
.withAppend(true)
|
||||
.setLayout(PatternLayout.newBuilder().withPattern("%d{ISO8601} [%p] %m%n").build())
|
||||
.setFilter(ThresholdFilter.createFilter(Level.DEBUG, Filter.Result.ACCEPT, Filter.Result.DENY))
|
||||
.withFileName(FileUtilities.USER_DIR + File.separator + Constants.LOG_FILE + ".log")
|
||||
.withFilePattern(FileUtilities.USER_DIR + File.separator + Constants.LOG_FILE + ".%i.log")
|
||||
.withStrategy(DefaultRolloverStrategy.newBuilder()
|
||||
.withMax(String.valueOf(logRotate))
|
||||
.withFileIndex("min")
|
||||
.build())
|
||||
.withPolicy(SizeBasedTriggeringPolicy.createPolicy(String.valueOf(logSize)))
|
||||
.withImmediateFlush(true)
|
||||
.build();
|
||||
fileAppender.start();
|
||||
|
||||
LoggerUtilities.getRootLogger().addAppender(fileAppender);
|
||||
}
|
||||
}
|
||||
70
old code/tray/src/qz/auth/CRL.java
Executable file
70
old code/tray/src/qz/auth/CRL.java
Executable file
@@ -0,0 +1,70 @@
|
||||
package qz.auth;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import qz.utils.ConnectionUtilities;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Wrapper class for the Certificate Revocation List
|
||||
* Created by Steven on 2/4/2015. Package: qz.auth Project: qz-print
|
||||
*/
|
||||
public class CRL {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(CRL.class);
|
||||
|
||||
/** The URL to the QZ CRL. Should not be changed except for dev tests */
|
||||
public static final String CRL_URL = "https://crl.qz.io";
|
||||
|
||||
private static CRL instance = null;
|
||||
|
||||
private ArrayList<String> revokedHashes = new ArrayList<String>();
|
||||
private boolean loaded = false;
|
||||
|
||||
|
||||
private CRL() {}
|
||||
|
||||
public static CRL getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new CRL();
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
log.info("Loading CRL from {}", CRL_URL);
|
||||
|
||||
try(BufferedReader br = new BufferedReader(new InputStreamReader(ConnectionUtilities.getInputStream(CRL_URL, false)))) {
|
||||
String line;
|
||||
while((line = br.readLine()) != null) {
|
||||
//Ignore empty and commented lines
|
||||
if (!line.isEmpty() && line.charAt(0) != '#') {
|
||||
instance.revokedHashes.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
instance.loaded = true;
|
||||
log.info("Successfully loaded {} CRL entries from {}", instance.revokedHashes.size(), CRL_URL);
|
||||
}
|
||||
catch(IOException e) {
|
||||
log.warn("Unable to access CRL from {}, {}", CRL_URL, e.toString());
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean isRevoked(String fingerprint) {
|
||||
return revokedHashes.contains(fingerprint);
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
536
old code/tray/src/qz/auth/Certificate.java
Executable file
536
old code/tray/src/qz/auth/Certificate.java
Executable file
@@ -0,0 +1,536 @@
|
||||
package qz.auth;
|
||||
|
||||
import org.apache.commons.codec.binary.StringUtils;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.Charsets;
|
||||
import org.apache.commons.ssl.Base64;
|
||||
import org.apache.commons.ssl.X509CertificateChainBuilder;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.bouncycastle.jce.PrincipalUtil;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.App;
|
||||
import qz.common.Constants;
|
||||
import qz.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by Steven on 1/27/2015. Package: qz.auth Project: qz-print
|
||||
* Wrapper to store certificate objects from
|
||||
*/
|
||||
public class Certificate {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(Certificate.class);
|
||||
private static final String QUIETLY_FAIL = "quiet";
|
||||
|
||||
public enum Algorithm {
|
||||
SHA1("SHA1withRSA"),
|
||||
SHA256("SHA256withRSA"),
|
||||
SHA512("SHA512withRSA");
|
||||
|
||||
String name;
|
||||
|
||||
Algorithm(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static ArrayList<Certificate> rootCAs = new ArrayList<>();
|
||||
public static Certificate builtIn;
|
||||
private static CertPathValidator validator;
|
||||
private static CertificateFactory factory;
|
||||
private static boolean trustBuiltIn = false;
|
||||
// id-at-description used for storing renewal information
|
||||
private static ASN1ObjectIdentifier RENEWAL_OF = new ASN1ObjectIdentifier("2.5.4.13");
|
||||
|
||||
public static final String[] saveFields = new String[] {"fingerprint", "commonName", "organization", "validFrom", "validTo", "valid"};
|
||||
|
||||
public static final String SPONSORED_CN_PREFIX = "Sponsored:";
|
||||
|
||||
// Valid date range allows UI to only show "Expired" text for valid certificates
|
||||
private static final Instant UNKNOWN_MIN = LocalDateTime.MIN.toInstant(ZoneOffset.UTC);
|
||||
private static final Instant UNKNOWN_MAX = LocalDateTime.MAX.toInstant(ZoneOffset.UTC);
|
||||
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
public static final DateTimeFormatter DATE_PARSE = DateTimeFormatter.ofPattern("uuuu-MM-dd['T'][ ]HH:mm:ss[.n]['Z']"); //allow parsing of both ISO and custom formatted dates
|
||||
|
||||
private X509Certificate theCertificate;
|
||||
private boolean sponsored;
|
||||
private String fingerprint;
|
||||
private String commonName;
|
||||
private String organization;
|
||||
private Instant validFrom;
|
||||
private Instant validTo;
|
||||
|
||||
//used by review sites UI only
|
||||
private boolean expired = false;
|
||||
private boolean valid = false;
|
||||
private boolean rootCA = false; // TODO: Move to constructor?
|
||||
|
||||
|
||||
//Pre-set certificate for use when missing
|
||||
public static final Certificate UNKNOWN;
|
||||
|
||||
static {
|
||||
HashMap<String,String> map = new HashMap<>();
|
||||
map.put("fingerprint", "UNKNOWN REQUEST");
|
||||
map.put("commonName", "An anonymous request");
|
||||
map.put("organization", "Unknown");
|
||||
map.put("validFrom", UNKNOWN_MIN.toString());
|
||||
map.put("validTo", UNKNOWN_MAX.toString());
|
||||
map.put("valid", "false");
|
||||
UNKNOWN = Certificate.loadCertificate(map);
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
validator = CertPathValidator.getInstance("PKIX");
|
||||
factory = CertificateFactory.getInstance("X.509");
|
||||
builtIn = new Certificate("-----BEGIN CERTIFICATE-----\n" +
|
||||
"MIIELzCCAxegAwIBAgIJALm151zCHDxiMA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD\n" +
|
||||
"VQQGEwJVUzELMAkGA1UECAwCTlkxEjAQBgNVBAcMCUNhbmFzdG90YTEbMBkGA1UE\n" +
|
||||
"CgwSUVogSW5kdXN0cmllcywgTExDMRswGQYDVQQLDBJRWiBJbmR1c3RyaWVzLCBM\n" +
|
||||
"TEMxGTAXBgNVBAMMEHF6aW5kdXN0cmllcy5jb20xJzAlBgkqhkiG9w0BCQEWGHN1\n" +
|
||||
"cHBvcnRAcXppbmR1c3RyaWVzLmNvbTAgFw0xNTAzMDEyMzM4MjlaGA8yMTE1MDMw\n" +
|
||||
"MjIzMzgyOVowgawxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTESMBAGA1UEBwwJ\n" +
|
||||
"Q2FuYXN0b3RhMRswGQYDVQQKDBJRWiBJbmR1c3RyaWVzLCBMTEMxGzAZBgNVBAsM\n" +
|
||||
"ElFaIEluZHVzdHJpZXMsIExMQzEZMBcGA1UEAwwQcXppbmR1c3RyaWVzLmNvbTEn\n" +
|
||||
"MCUGCSqGSIb3DQEJARYYc3VwcG9ydEBxemluZHVzdHJpZXMuY29tMIIBIjANBgkq\n" +
|
||||
"hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWsBa6uk+RM4OKBZTRfIIyqaaFD71FAS\n" +
|
||||
"7kojAQ+ySMpYuqLjIVZuCh92o1FGBvyBKUFc6knAHw5749yhLCYLXhzWwiNW2ri1\n" +
|
||||
"Jwx/d83Wnaw6qA3lt++u3tmiA8tsFtss0QZW0YBpFsIqhamvB3ypwu0bdUV/oH7g\n" +
|
||||
"/s8TFR5LrDfnfxlLFYhTUVWuWzMqEFAGnFG3uw/QMWZnQgkGbx0LMcYzdqFb7/vz\n" +
|
||||
"rTSHfjJsisUTWPjo7SBnAtNYCYaGj0YH5RFUdabnvoTdV2XpA5IPYa9Q597g/M0z\n" +
|
||||
"icAjuaK614nKXDaAUCbjki8RL3OK9KY920zNFboq/jKG6rKW2t51ZQIDAQABo1Aw\n" +
|
||||
"TjAdBgNVHQ4EFgQUA0XGTcD6jqkL2oMPQaVtEgZDqV4wHwYDVR0jBBgwFoAUA0XG\n" +
|
||||
"TcD6jqkL2oMPQaVtEgZDqV4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\n" +
|
||||
"AQEAijcT5QMVqrWWqpNEe1DidzQfSnKo17ZogHW+BfUbxv65JbDIntnk1XgtLTKB\n" +
|
||||
"VAdIWUtGZbXxrp16NEsh96V2hjDIoiAaEpW+Cp6AHhIVgVh7Q9Knq9xZ1t6H8PL5\n" +
|
||||
"QiYQKQgJ0HapdCxlPKBfUm/Mj1ppNl9mPFJwgHmzORexbxrzU/M5i2jlies+CXNq\n" +
|
||||
"cvmF2l33QNHnLwpFGwYKs08pyHwUPp6+bfci6lRvavztgvnKroWWIRq9ZPlC0yVK\n" +
|
||||
"FFemhbCd7ZVbrTo0NcWZM1PTAbvlOikV9eh3i1Vot+3dJ8F27KwUTtnV0B9Jrxum\n" +
|
||||
"W9P3C48mvwTxYZJFOu0N9UBLLg==\n" +
|
||||
"-----END CERTIFICATE-----");
|
||||
|
||||
builtIn.valid = true;
|
||||
setTrustBuiltIn(true);
|
||||
scanAdditionalCAs();
|
||||
}
|
||||
catch(NoSuchAlgorithmException | CertificateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void scanAdditionalCAs() {
|
||||
ArrayList<Map.Entry<Path, String>> certPaths = new ArrayList<>();
|
||||
// First, look for "authcert.override", "-DtrustedRootCert"
|
||||
certPaths.addAll(FileUtilities.parseDelimitedPaths(PrefsSearch.getString(ArgValue.AUTHCERT_OVERRIDE, App.getTrayProperties())));
|
||||
|
||||
// Second, look for "override.crt" within App directory
|
||||
certPaths.add(new AbstractMap.SimpleEntry<>(SystemUtilities.getJarParentPath().resolve(Constants.OVERRIDE_CERT), QUIETLY_FAIL));
|
||||
|
||||
for(Map.Entry<Path, String> certPath : certPaths) {
|
||||
if(certPath.getKey() != null) {
|
||||
if (certPath.getKey().toFile().exists()) {
|
||||
try {
|
||||
Certificate caCert = new Certificate(FileUtilities.readLocalFile(certPath.getKey()));
|
||||
caCert.rootCA = true;
|
||||
caCert.valid = true;
|
||||
if(!rootCAs.contains(caCert)) {
|
||||
log.debug("Adding CA certificate: CN={}, O={} ({})",
|
||||
caCert.getCommonName(), caCert.getOrganization(), caCert.getFingerprint());
|
||||
rootCAs.add(caCert);
|
||||
} else {
|
||||
log.warn("CA cert exists, skipping: {}", certPath.getKey());
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.error("Error loading CA cert: {}", certPath.getKey(), e);
|
||||
}
|
||||
} else if(!certPath.getValue().equals(QUIETLY_FAIL)) {
|
||||
log.warn("CA cert \"{}\" was provided, but could not be found, skipping.", certPath.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Certificate(Path path) throws IOException, CertificateException {
|
||||
this(new String(Files.readAllBytes(path), Charsets.UTF_8));
|
||||
}
|
||||
|
||||
/** Decodes a certificate and intermediate certificate from the given string */
|
||||
public Certificate(String in) throws CertificateException {
|
||||
try {
|
||||
//Strip beginning and end
|
||||
String[] split = in.split("--START INTERMEDIATE CERT--");
|
||||
byte[] serverCertificate = Base64.decodeBase64(split[0].replaceAll(X509Constants.BEGIN_CERT, "").replaceAll(X509Constants.END_CERT, ""));
|
||||
|
||||
X509Certificate theIntermediateCertificate;
|
||||
if (split.length == 2) {
|
||||
byte[] intermediateCertificate = Base64.decodeBase64(split[1].replaceAll(X509Constants.BEGIN_CERT, "").replaceAll(X509Constants.END_CERT, ""));
|
||||
theIntermediateCertificate = (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(intermediateCertificate));
|
||||
} else {
|
||||
theIntermediateCertificate = null; //Self-signed
|
||||
}
|
||||
|
||||
//Generate cert
|
||||
theCertificate = (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(serverCertificate));
|
||||
commonName = getSubjectX509Principal(theCertificate, BCStyle.CN);
|
||||
if(commonName.isEmpty()) {
|
||||
throw new CertificateException("Common Name cannot be blank.");
|
||||
}
|
||||
// Remove "Sponsored: " from CN, we'll swap the trusted icon instead <3
|
||||
if(commonName.startsWith(SPONSORED_CN_PREFIX)) {
|
||||
commonName = commonName.split(SPONSORED_CN_PREFIX)[1].trim();
|
||||
sponsored = true;
|
||||
} else {
|
||||
sponsored = false;
|
||||
}
|
||||
fingerprint = makeThumbPrint(theCertificate);
|
||||
organization = getSubjectX509Principal(theCertificate, BCStyle.O);
|
||||
validFrom = theCertificate.getNotBefore().toInstant();
|
||||
validTo = theCertificate.getNotAfter().toInstant();
|
||||
|
||||
// Check trust anchor against all root certs
|
||||
Certificate foundRoot = null;
|
||||
if(!this.rootCA) {
|
||||
for(Certificate rootCA : rootCAs) {
|
||||
HashSet<X509Certificate> chain = new HashSet<>();
|
||||
try {
|
||||
chain.add(rootCA.theCertificate);
|
||||
if (theIntermediateCertificate != null) { chain.add(theIntermediateCertificate); }
|
||||
X509Certificate[] x509Certificates = X509CertificateChainBuilder.buildPath(theCertificate, chain);
|
||||
|
||||
Set<TrustAnchor> anchor = new HashSet<>();
|
||||
anchor.add(new TrustAnchor(rootCA.theCertificate, null));
|
||||
PKIXParameters params = new PKIXParameters(anchor);
|
||||
params.setRevocationEnabled(false); // TODO: Re-enable, remove proprietary CRL
|
||||
validator.validate(factory.generateCertPath(Arrays.asList(x509Certificates)), params);
|
||||
foundRoot = rootCA;
|
||||
valid = true;
|
||||
log.debug("Successfully chained certificate: CN={}, O={} ({})", getCommonName(), getOrganization(), getFingerprint());
|
||||
break; // if successful, don't attempt another chain
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.warn("Problem building certificate chain (normal if multiple CAs are in use)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for expiration
|
||||
Instant now = Instant.now();
|
||||
if (expired = (validFrom.isAfter(now) || validTo.isBefore(now))) {
|
||||
log.warn("Certificate is expired: CN={}, O={} ({})", getCommonName(), getOrganization(), getFingerprint());
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// If cert matches a rootCA trust it blindly
|
||||
// If cert is chained to a 3rd party rootCA, trust it blindly as well
|
||||
Iterator<Certificate> allCerts = rootCAs.iterator();
|
||||
while(allCerts.hasNext()) {
|
||||
Certificate cert = allCerts.next();
|
||||
if(cert.equals(this) || (cert.equals(foundRoot) && !cert.equals(builtIn))) {
|
||||
log.debug("Adding {} to {} list", cert.toString(), Constants.ALLOW_FILE);
|
||||
if(!isSaved()) {
|
||||
FileUtilities.printLineToFile(Constants.ALLOW_FILE, data());
|
||||
}
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
readRenewalInfo();
|
||||
CRL qzCrl = CRL.getInstance();
|
||||
if (qzCrl.isLoaded()) {
|
||||
if (qzCrl.isRevoked(getFingerprint()) || (theIntermediateCertificate != null && qzCrl.isRevoked(makeThumbPrint(theIntermediateCertificate)))) {
|
||||
log.error("Certificate has been revoked and can no longer be used: CN={}, O={} ({})", getCommonName(), getOrganization(), getFingerprint());
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
//Assume nothing is revoked, because we can't get the CRL
|
||||
log.warn("Failed to retrieve QZ CRL, skipping CRL check");
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
CertificateException certificateException = new CertificateException();
|
||||
certificateException.initCause(e);
|
||||
throw certificateException;
|
||||
}
|
||||
}
|
||||
|
||||
private void readRenewalInfo() throws Exception {
|
||||
Vector values = PrincipalUtil.getSubjectX509Principal(theCertificate).getValues(RENEWAL_OF);
|
||||
Iterator renewals = values.iterator();
|
||||
|
||||
while(renewals.hasNext()) {
|
||||
String renewalInfo = String.valueOf(renewals.next());
|
||||
|
||||
String renewalPrefix = "renewal-of-";
|
||||
if (!renewalInfo.startsWith(renewalPrefix)) {
|
||||
log.warn("Malformed renewal info: {}", renewalInfo);
|
||||
continue;
|
||||
}
|
||||
String previousFingerprint = renewalInfo.substring(renewalPrefix.length());
|
||||
if (previousFingerprint.length() != 40) {
|
||||
log.warn("Malformed renewal fingerprint: {}", previousFingerprint);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add this certificate to the whitelist if the previous certificate was whitelisted
|
||||
|
||||
// First, handle shared directory
|
||||
File sharedFile = FileUtilities.getFile(Constants.ALLOW_FILE, false);
|
||||
if (existsInAnyFile(previousFingerprint, sharedFile) && !isSaved(false)) {
|
||||
if(!FileUtilities.printLineToFile(Constants.ALLOW_FILE, data(), false)) {
|
||||
// Fallback to local directory if shared is not writable
|
||||
FileUtilities.printLineToFile(Constants.ALLOW_FILE, data(), /* fallback */ true);
|
||||
}
|
||||
}
|
||||
|
||||
// Second, handle local directory
|
||||
File localFile = FileUtilities.getFile(Constants.ALLOW_FILE, true);
|
||||
if (existsInAnyFile(previousFingerprint, localFile) && !isSaved(true)) {
|
||||
FileUtilities.printLineToFile(Constants.ALLOW_FILE, data(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Certificate() {}
|
||||
|
||||
|
||||
/**
|
||||
* Used to rebuild a certificate for the 'Saved Sites' screen without having to decrypt the certificates again
|
||||
*/
|
||||
public static Certificate loadCertificate(HashMap<String,String> data) {
|
||||
Certificate cert = new Certificate();
|
||||
|
||||
cert.fingerprint = data.get("fingerprint");
|
||||
cert.commonName = data.get("commonName");
|
||||
cert.organization = data.get("organization");
|
||||
|
||||
try {
|
||||
cert.validFrom = Instant.from(LocalDateTime.from(DATE_PARSE.parse(data.get("validFrom"))).atZone(ZoneOffset.UTC));
|
||||
cert.validTo = Instant.from(LocalDateTime.from(DATE_PARSE.parse(data.get("validTo"))).atZone(ZoneOffset.UTC));
|
||||
}
|
||||
catch(DateTimeException e) {
|
||||
cert.validFrom = UNKNOWN_MIN;
|
||||
cert.validTo = UNKNOWN_MAX;
|
||||
|
||||
log.warn("Unable to parse certificate date: {}", e.getMessage());
|
||||
}
|
||||
|
||||
cert.valid = Boolean.parseBoolean(data.get("valid"));
|
||||
|
||||
return cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks given signature for given data against this certificate,
|
||||
* ensuring it is properly signed
|
||||
*
|
||||
* @param signature the signature appended to the data, base64 encoded
|
||||
* @param data the data to check
|
||||
* @return true if signature valid, false if not
|
||||
*/
|
||||
public boolean isSignatureValid(Algorithm algorithm, String signature, String data) {
|
||||
if (!signature.isEmpty()) {
|
||||
//On errors, assume failure.
|
||||
try {
|
||||
Signature verifier = Signature.getInstance(algorithm.name);
|
||||
verifier.initVerify(theCertificate.getPublicKey());
|
||||
verifier.update(StringUtils.getBytesUtf8(DigestUtils.sha256Hex(data)));
|
||||
|
||||
return verifier.verify(Base64.decodeBase64(signature));
|
||||
}
|
||||
catch(GeneralSecurityException e) {
|
||||
log.error("Unable to verify signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if the certificate has been added to the specified allow file */
|
||||
public boolean isSaved(boolean local) {
|
||||
File allowed = FileUtilities.getFile(Constants.ALLOW_FILE, local);
|
||||
return existsInAnyFile(getFingerprint(), allowed);
|
||||
}
|
||||
|
||||
/** Checks if the certificate has been added to any allow file */
|
||||
public boolean isSaved() {
|
||||
return isSaved(false) || isSaved(true);
|
||||
}
|
||||
|
||||
/** Checks if the certificate has been added to the local block file */
|
||||
public boolean isBlocked() {
|
||||
File blocks = FileUtilities.getFile(Constants.BLOCK_FILE, true);
|
||||
File blocksShared = FileUtilities.getFile(Constants.BLOCK_FILE, false);
|
||||
return existsInAnyFile(getFingerprint(), blocksShared, blocks);
|
||||
}
|
||||
|
||||
private static boolean existsInAnyFile(String fingerprint, File... files) {
|
||||
for(File file : files) {
|
||||
if (file == null) { continue; }
|
||||
|
||||
try(BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
while((line = br.readLine()) != null) {
|
||||
if (line.contains("\t")) {
|
||||
String print = line.substring(0, line.indexOf("\t"));
|
||||
if (print.equals(fingerprint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public String getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
public String getCommonName() {
|
||||
return commonName;
|
||||
}
|
||||
|
||||
public String getOrganization() {
|
||||
return organization;
|
||||
}
|
||||
|
||||
public String getValidFrom() {
|
||||
if (validFrom.isAfter(UNKNOWN_MIN)) {
|
||||
return DATE_FORMAT.format(validFrom.atZone(ZoneOffset.UTC));
|
||||
} else {
|
||||
return "Not Provided";
|
||||
}
|
||||
}
|
||||
|
||||
public String getValidTo() {
|
||||
if (validTo.isBefore(UNKNOWN_MAX)) {
|
||||
return DATE_FORMAT.format(validTo.atZone(ZoneOffset.UTC));
|
||||
} else {
|
||||
return "Not Provided";
|
||||
}
|
||||
}
|
||||
|
||||
public Instant getValidFromDate() {
|
||||
return validFrom;
|
||||
}
|
||||
|
||||
public Instant getValidToDate() {
|
||||
return validTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates certificate against embedded cert.
|
||||
*/
|
||||
public boolean isTrusted() {
|
||||
return isValid() && !isExpired();
|
||||
}
|
||||
|
||||
public boolean isSponsored() {
|
||||
return sponsored;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return expired;
|
||||
}
|
||||
|
||||
|
||||
public static String makeThumbPrint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(cert.getEncoded());
|
||||
return ByteUtilities.bytesToHex(md.digest(), false);
|
||||
}
|
||||
|
||||
private String data(boolean assumeTrusted) {
|
||||
return getFingerprint() + "\t" +
|
||||
getCommonName() + "\t" +
|
||||
getOrganization() + "\t" +
|
||||
getValidFrom() + "\t" +
|
||||
getValidTo() + "\t" +
|
||||
// Used by equals(), may fail if it hasn't been trusted yet
|
||||
(assumeTrusted ? true : isTrusted());
|
||||
}
|
||||
|
||||
public String data() {
|
||||
return data(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getOrganization() + " (" + getCommonName() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Certificate) {
|
||||
return ((Certificate)obj).data(true).equals(data(true));
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
public static void setTrustBuiltIn(boolean trustBuiltIn) {
|
||||
if(trustBuiltIn) {
|
||||
if (!rootCAs.contains(builtIn)) {
|
||||
log.debug("Adding internal CA certificate: CN={}, O={} ({})",
|
||||
builtIn.getCommonName(), builtIn.getOrganization(), builtIn.getFingerprint());
|
||||
builtIn.rootCA = true;
|
||||
builtIn.valid = true;
|
||||
rootCAs.add(0, builtIn);
|
||||
}
|
||||
} else {
|
||||
if (rootCAs.contains(builtIn)) {
|
||||
log.debug("Removing internal CA certificate: CN={}, O={} ({})",
|
||||
builtIn.getCommonName(), builtIn.getOrganization(), builtIn.getFingerprint());
|
||||
rootCAs.remove(builtIn);
|
||||
}
|
||||
}
|
||||
Certificate.trustBuiltIn = trustBuiltIn;
|
||||
}
|
||||
|
||||
public static boolean isTrustBuiltIn() {
|
||||
return trustBuiltIn;
|
||||
}
|
||||
|
||||
public static boolean hasAdditionalCAs() {
|
||||
return rootCAs.size() > (isTrustBuiltIn() ? 1 : 0);
|
||||
}
|
||||
|
||||
private static String getSubjectX509Principal(X509Certificate cert, ASN1ObjectIdentifier key) {
|
||||
try {
|
||||
Vector v = PrincipalUtil.getSubjectX509Principal(cert).getValues(key);
|
||||
if(v.size() > 0) {
|
||||
return String.valueOf(v.get(0));
|
||||
}
|
||||
} catch(CertificateEncodingException e) {
|
||||
log.warn("Certificate encoding exception occurred", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
87
old code/tray/src/qz/auth/PairingAuth.java
Executable file
87
old code/tray/src/qz/auth/PairingAuth.java
Executable file
@@ -0,0 +1,87 @@
|
||||
package qz.auth;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import qz.utils.FileUtilities;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
|
||||
public class PairingAuth {
|
||||
private static String site = null;
|
||||
private static String pairingKey = null;
|
||||
private static final String HMAC_ALGO = "HmacSHA256";
|
||||
private static File configFile = null;
|
||||
|
||||
static {
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
private static void loadConfig() {
|
||||
try {
|
||||
// Use QZ Tray's user directory - same location as PairingConfigDialog
|
||||
configFile = new File(FileUtilities.USER_DIR.toFile(), "pairing-config.json");
|
||||
|
||||
if (!configFile.exists()) {
|
||||
System.out.println("Pairing config file not found at: " + configFile.getAbsolutePath());
|
||||
site = null;
|
||||
pairingKey = null;
|
||||
return;
|
||||
}
|
||||
|
||||
String content = new String(Files.readAllBytes(configFile.toPath()));
|
||||
JSONObject config = new JSONObject(content);
|
||||
site = config.optString("site", null);
|
||||
pairingKey = config.optString("pairing_key", null);
|
||||
|
||||
System.out.println("Pairing config loaded from: " + configFile.getAbsolutePath());
|
||||
System.out.println("Site configured: " + site);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error loading pairing config: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
site = null;
|
||||
pairingKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
public static String getPairingKey() {
|
||||
return pairingKey;
|
||||
}
|
||||
|
||||
public static boolean isConfigured() {
|
||||
return site != null && !site.isEmpty() && pairingKey != null && !pairingKey.isEmpty();
|
||||
}
|
||||
|
||||
public static String getConfigFilePath() {
|
||||
return configFile != null ? configFile.getAbsolutePath() : "unknown";
|
||||
}
|
||||
|
||||
public static void reload() {
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static String hmacSignature(String message) {
|
||||
if (pairingKey == null || pairingKey.isEmpty()) {
|
||||
System.err.println("Cannot compute HMAC: pairing key is not configured");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Mac mac = Mac.getInstance(HMAC_ALGO);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(pairingKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGO);
|
||||
mac.init(keySpec);
|
||||
byte[] hmac = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(hmac);
|
||||
} catch (Exception e) {
|
||||
System.err.println("HMAC computation failed: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
118
old code/tray/src/qz/auth/RequestState.java
Executable file
118
old code/tray/src/qz/auth/RequestState.java
Executable file
@@ -0,0 +1,118 @@
|
||||
package qz.auth;
|
||||
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import qz.common.Constants;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RequestState {
|
||||
|
||||
public enum Validity {
|
||||
TRUSTED("Valid"),
|
||||
EXPIRED("Expired Signature"),
|
||||
UNSIGNED("Invalid Signature"),
|
||||
EXPIRED_CERT("Expired Certificate"),
|
||||
FUTURE_CERT("Future Certificate"),
|
||||
INVALID_CERT("Invalid Certificate"),
|
||||
UNKNOWN("Invalid");
|
||||
|
||||
private String formatted;
|
||||
|
||||
Validity(String formatted) {
|
||||
this.formatted = formatted;
|
||||
}
|
||||
|
||||
public String getFormatted() {
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
|
||||
Certificate certUsed;
|
||||
JSONObject requestData;
|
||||
|
||||
boolean initialConnect;
|
||||
Validity status;
|
||||
|
||||
public RequestState(Certificate cert, JSONObject data) {
|
||||
certUsed = cert;
|
||||
requestData = data;
|
||||
status = Validity.UNKNOWN;
|
||||
}
|
||||
|
||||
public Certificate getCertUsed() {
|
||||
return certUsed;
|
||||
}
|
||||
|
||||
public JSONObject getRequestData() {
|
||||
return requestData;
|
||||
}
|
||||
|
||||
public boolean isInitialConnect() {
|
||||
return initialConnect;
|
||||
}
|
||||
|
||||
public void markNewConnection(Certificate cert) {
|
||||
certUsed = cert;
|
||||
initialConnect = true;
|
||||
|
||||
checkCertificateState(cert);
|
||||
}
|
||||
|
||||
public void checkCertificateState(Certificate cert) {
|
||||
if (cert.isTrusted()) {
|
||||
status = Validity.TRUSTED;
|
||||
} else if (cert.getValidToDate().isBefore(Instant.now())) {
|
||||
status = Validity.EXPIRED_CERT;
|
||||
} else if (cert.getValidFromDate().isAfter(Instant.now())) {
|
||||
status = Validity.FUTURE_CERT;
|
||||
} else if (!cert.isValid()) {
|
||||
status = Validity.INVALID_CERT;
|
||||
} else {
|
||||
status = Validity.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public Validity getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Validity state) {
|
||||
status = state;
|
||||
}
|
||||
|
||||
public boolean hasCertificate() {
|
||||
return certUsed != null && certUsed != Certificate.UNKNOWN;
|
||||
}
|
||||
|
||||
public boolean hasSavedCert() {
|
||||
return isVerified() && certUsed.isSaved();
|
||||
}
|
||||
|
||||
public boolean hasBlockedCert() {
|
||||
return certUsed == null || certUsed.isBlocked();
|
||||
}
|
||||
|
||||
public String getCertName() {
|
||||
return certUsed.getCommonName();
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return certUsed.isTrusted() && status == Validity.TRUSTED;
|
||||
}
|
||||
|
||||
public boolean isSponsored() {
|
||||
return certUsed.isSponsored();
|
||||
}
|
||||
|
||||
public String getValidityInfo() {
|
||||
if (status == Validity.TRUSTED) {
|
||||
return Constants.TRUSTED_CERT;
|
||||
} else if (Arrays.asList(Validity.UNSIGNED, Validity.EXPIRED, Validity.EXPIRED_CERT, Validity.FUTURE_CERT).contains(status)) {
|
||||
return Constants.NO_TRUST + " - " + status.getFormatted();
|
||||
} else {
|
||||
return Constants.UNTRUSTED_CERT;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
old code/tray/src/qz/auth/X509Constants.java
Executable file
12
old code/tray/src/qz/auth/X509Constants.java
Executable file
@@ -0,0 +1,12 @@
|
||||
package qz.auth;
|
||||
|
||||
/**
|
||||
* Created by Tres on 3/3/2015.
|
||||
*/
|
||||
public class X509Constants {
|
||||
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
|
||||
public static final String END_CERT = "-----END CERTIFICATE-----";
|
||||
public static final String INTERMEDIATE_CERT = "--START INTERMEDIATE CERT--";
|
||||
public static final String BEGIN_CRL = "-----BEGIN X509 CRL-----";
|
||||
public static final String END_CRL = "-----END X509 CRL-----";
|
||||
}
|
||||
129
old code/tray/src/qz/build/Fetcher.java
Executable file
129
old code/tray/src/qz/build/Fetcher.java
Executable file
@@ -0,0 +1,129 @@
|
||||
package qz.build;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.utils.ShellUtilities;
|
||||
import qz.utils.SystemUtilities;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
/**
|
||||
* Fetches a zip or tarball from URL and decompresses it
|
||||
*/
|
||||
public class Fetcher {
|
||||
public enum Format {
|
||||
ZIP(".zip"),
|
||||
TARBALL(".tar.gz"),
|
||||
UNKNOWN(null);
|
||||
|
||||
String suffix;
|
||||
Format(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public static Format parse(String url) {
|
||||
for(Format format : Format.values()) {
|
||||
if (url.endsWith(format.getSuffix())) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger log = LogManager.getLogger(Fetcher.class);
|
||||
|
||||
public static void main(String ... args) throws IOException {
|
||||
new Fetcher("jlink/qz-tray-src_x.x.x", "https://github.com/qzind/tray/archive/master.tar.gz").fetch().uncompress();
|
||||
}
|
||||
|
||||
String resourceName;
|
||||
String url;
|
||||
Format format;
|
||||
Path rootDir;
|
||||
File tempArchive;
|
||||
File tempExtracted;
|
||||
File extracted;
|
||||
|
||||
public Fetcher(String resourceName, String url) {
|
||||
this.url = url;
|
||||
this.resourceName = resourceName;
|
||||
this.format = Format.parse(url);
|
||||
// Try to calculate out/
|
||||
this.rootDir = SystemUtilities.getJarParentPath().getParent();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Fetcher(String resourceName, String url, Format format, String rootDir) {
|
||||
this.resourceName = resourceName;
|
||||
this.url = url;
|
||||
this.format = format;
|
||||
this.rootDir = Paths.get(rootDir);
|
||||
}
|
||||
|
||||
public Fetcher fetch() throws IOException {
|
||||
extracted = new File(rootDir.toString(), resourceName);
|
||||
if(extracted.isDirectory() && extracted.exists()) {
|
||||
log.info("Resource '{}' from [{}] has already been downloaded and extracted. Using: [{}]", resourceName, url, extracted);
|
||||
} else {
|
||||
tempExtracted = new File(rootDir.toString(), resourceName + "~tmp");
|
||||
if(tempExtracted.exists()) {
|
||||
FileUtils.deleteDirectory(tempExtracted);
|
||||
}
|
||||
// temp directory to thwart partial extraction
|
||||
tempExtracted.mkdirs();
|
||||
tempArchive = File.createTempFile(resourceName, ".zip");
|
||||
log.info("Fetching '{}' from [{}] and saving to [{}]", resourceName, url, tempArchive);
|
||||
FileUtils.copyURLToFile(new URL(url), tempArchive);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String uncompress() throws IOException {
|
||||
if(tempArchive != null) {
|
||||
log.info("Unzipping '{}' from [{}] to [{}]", resourceName, tempArchive, tempExtracted);
|
||||
if(format == Format.ZIP) {
|
||||
unzip(tempArchive.getAbsolutePath(), tempExtracted);
|
||||
} else {
|
||||
untar(tempArchive.getAbsolutePath(), tempExtracted);
|
||||
}
|
||||
log.info("Moving [{}] to [{}]", tempExtracted, extracted);
|
||||
tempExtracted.renameTo(extracted);
|
||||
}
|
||||
return extracted.toString();
|
||||
}
|
||||
|
||||
public static void untar(String sourceFile, File targetDir) throws IOException {
|
||||
// TODO: Switch to TarArchiveInputStream from Apache Commons Compress
|
||||
if (!ShellUtilities.execute("tar", "-xzf", sourceFile, "-C", targetDir.getPath())) {
|
||||
throw new IOException("Something went wrong extracting " + sourceFile +", check logs for details");
|
||||
}
|
||||
}
|
||||
|
||||
public static void unzip(String sourceFile, File targetDir) throws IOException {
|
||||
try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(sourceFile))) {
|
||||
for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) {
|
||||
Path resolvedPath = targetDir.toPath().resolve(ze.getName());
|
||||
if (ze.isDirectory()) {
|
||||
Files.createDirectories(resolvedPath);
|
||||
} else {
|
||||
Files.createDirectories(resolvedPath.getParent());
|
||||
Files.copy(zipIn, resolvedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
341
old code/tray/src/qz/build/JLink.java
Executable file
341
old code/tray/src/qz/build/JLink.java
Executable file
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* @author Tres Finocchiaro
|
||||
*
|
||||
* Copyright (C) 2020 Tres Finocchiaro, QZ Industries, LLC
|
||||
*
|
||||
* LGPL 2.1 This is free software. This software and source code are released under
|
||||
* the "LGPL 2.1 License". A copy of this license should be distributed with
|
||||
* this software. http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
|
||||
package qz.build;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.build.jlink.Platform;
|
||||
import qz.build.jlink.Vendor;
|
||||
import qz.build.jlink.Url;
|
||||
import qz.build.provision.params.Arch;
|
||||
import qz.common.Constants;
|
||||
import qz.utils.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
public class JLink {
|
||||
private static final Logger log = LogManager.getLogger(JLink.class);
|
||||
public static final Vendor JAVA_DEFAULT_VENDOR = Vendor.BELLSOFT;
|
||||
private static final String JAVA_DEFAULT_VERSION = "11.0.17+7";
|
||||
private static final String JAVA_DEFAULT_GC_ENGINE = "hotspot"; // or "openj9"
|
||||
private static final String JAVA_DEFAULT_GC_VERSION = "0.35.0"; // openj9 gc only
|
||||
|
||||
private Path jarPath;
|
||||
private Path jdepsPath;
|
||||
private Path jlinkPath;
|
||||
private Path jmodsPath;
|
||||
private Path outPath;
|
||||
private Version jdepsVersion;
|
||||
private Platform hostPlatform;
|
||||
private Platform targetPlatform;
|
||||
private Arch hostArch;
|
||||
private Arch targetArch;
|
||||
private Vendor javaVendor;
|
||||
private String gcEngine;
|
||||
private String javaVersion;
|
||||
private String gcVersion;
|
||||
|
||||
private Path targetJdk;
|
||||
|
||||
private Version javaSemver;
|
||||
|
||||
private LinkedHashSet<String> depList;
|
||||
|
||||
public JLink(String targetPlatform, String targetArch, String javaVendor, String javaVersion, String gcEngine, String gcVersion, String targetJdk) throws IOException {
|
||||
this.hostPlatform = Platform.getCurrentPlatform();
|
||||
this.hostArch = SystemUtilities.getArch();
|
||||
|
||||
this.targetPlatform = Platform.parse(targetPlatform, this.hostPlatform);
|
||||
this.targetArch = Arch.parse(targetArch, this.hostArch);
|
||||
this.javaVendor = Vendor.parse(javaVendor, JAVA_DEFAULT_VENDOR);
|
||||
this.gcEngine = getParam("gcEngine", gcEngine, JAVA_DEFAULT_GC_ENGINE);
|
||||
this.javaVersion = getParam("javaVersion", javaVersion, JAVA_DEFAULT_VERSION);
|
||||
this.gcVersion = getParam("gcVersion", gcVersion, JAVA_DEFAULT_GC_VERSION);
|
||||
|
||||
this.javaSemver = SystemUtilities.getJavaVersion(this.javaVersion);
|
||||
|
||||
// Optional: Provide the location of a custom JDK on the local filesystem
|
||||
if(!StringUtils.isEmpty(targetJdk)) {
|
||||
Path jdkPath = Paths.get(targetJdk);
|
||||
Properties jdkProps = new Properties();
|
||||
jdkProps.load(new FileInputStream(jdkPath.resolve("release").toFile()));
|
||||
String customVersion = jdkProps.getProperty("JAVA_VERSION");
|
||||
if(customVersion.contains("\"")) {
|
||||
customVersion = customVersion.split("\"")[1];
|
||||
}
|
||||
Version customSemver = SystemUtilities.getJavaVersion(customVersion);
|
||||
if(needsDownload(javaSemver, customSemver)) {
|
||||
// The "release" file doesn't have build info, so we can't auto-download :(
|
||||
if(javaSemver.getMajorVersion() != customSemver.getMajorVersion()) {
|
||||
log.error("Error: jlink version {}.0 does not match target java.base version {}.0", javaSemver.getMajorVersion(), customSemver.getMajorVersion());
|
||||
} else {
|
||||
// Handle edge-cases (e.g. JDK-8240734)
|
||||
log.error("Error: jlink version {} is incompatible with target java.base version {}", javaSemver.getMajorVersion(), customSemver.getMajorVersion());
|
||||
}
|
||||
System.exit(2);
|
||||
}
|
||||
this.targetJdk = Paths.get(targetJdk);
|
||||
calculateToolPaths(Paths.get(targetJdk));
|
||||
} else {
|
||||
// Determine if the version we're building with is compatible with the target version
|
||||
if (needsDownload(javaSemver, Constants.JAVA_VERSION)) {
|
||||
log.warn("Java versions are incompatible, locating a suitable runtime for Java " + javaSemver.getMajorVersion() + "...");
|
||||
String hostJdk = downloadJdk(this.hostArch, this.hostPlatform);
|
||||
calculateToolPaths(Paths.get(hostJdk));
|
||||
} else {
|
||||
calculateToolPaths(null);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.targetJdk == null) {
|
||||
targetJdk = downloadJdk(this.targetArch, this.targetPlatform);
|
||||
jmodsPath = Paths.get(targetJdk, "jmods");
|
||||
} else {
|
||||
log.info("\"targetjdk\" was provided {}, skipping download", targetJdk);
|
||||
jmodsPath = this.targetJdk.resolve("jmods");
|
||||
}
|
||||
|
||||
log.info("Selecting jmods folder: {}", jmodsPath);
|
||||
|
||||
calculateJarPath()
|
||||
.calculateOutPath()
|
||||
.calculateDepList()
|
||||
.deployJre();
|
||||
}
|
||||
|
||||
public static void main(String ... args) throws IOException {
|
||||
new JLink(null, null, null, null, null, null, null).calculateJarPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incompatibilities between JDKs, download a fresh one if needed
|
||||
*/
|
||||
private static boolean needsDownload(Version want, Version installed) {
|
||||
// jdeps and jlink historically require matching major JDK versions. Download if needed.
|
||||
boolean downloadJdk = installed.getMajorVersion() != want.getMajorVersion();
|
||||
|
||||
// Per JDK-8240734: Major versions checks aren't enough starting with 11.0.16+8
|
||||
// see also https://github.com/adoptium/adoptium-support/issues/557
|
||||
Version bad = SystemUtilities.getJavaVersion("11.0.16+8");
|
||||
if(want.greaterThanOrEqualTo(bad) && installed.lessThan(bad) ||
|
||||
installed.greaterThanOrEqualTo(bad) && want.lessThan(bad)) {
|
||||
// Force download
|
||||
// Fixes "Hash of java.rmi differs from expected hash"
|
||||
downloadJdk = true;
|
||||
}
|
||||
return downloadJdk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the JDK and return the path it was extracted to
|
||||
*/
|
||||
private String downloadJdk(Arch arch, Platform platform) throws IOException {
|
||||
String url = new Url(this.javaVendor).format(arch, platform, this.gcEngine, this.javaSemver, this.javaVersion, this.gcVersion);
|
||||
|
||||
// Saves to out e.g. "out/jlink/jdk-AdoptOpenjdk-amd64-platform-11_0_7"
|
||||
String extractedJdk = new Fetcher(String.format("jlink/jdk-%s-%s-%s-%s", javaVendor.value(), arch, platform.value(), javaSemver.toString().replaceAll("\\+", "_")), url)
|
||||
.fetch()
|
||||
.uncompress();
|
||||
|
||||
// Get first subfolder, e.g. jdk-11.0.7+10
|
||||
for(File subfolder : new File(extractedJdk).listFiles(pathname -> pathname.isDirectory())) {
|
||||
extractedJdk = subfolder.getPath();
|
||||
if(platform == Platform.MAC && Paths.get(extractedJdk, "/Contents/Home").toFile().isDirectory()) {
|
||||
extractedJdk += "/Contents/Home";
|
||||
}
|
||||
log.info("Selecting JDK home: {}", extractedJdk);
|
||||
break;
|
||||
}
|
||||
|
||||
return extractedJdk;
|
||||
}
|
||||
|
||||
private JLink calculateJarPath() {
|
||||
if(SystemUtilities.isJar()) {
|
||||
jarPath = SystemUtilities.getJarPath();
|
||||
} else {
|
||||
// Detect out/dist/qz-tray.jar for IDE usage
|
||||
jarPath = SystemUtilities.getJarParentPath()
|
||||
.resolve("dist")
|
||||
.resolve(Constants.PROPS_FILE + ".jar");
|
||||
}
|
||||
log.info("Assuming jar path: {}", jarPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
private JLink calculateOutPath() {
|
||||
switch(targetPlatform) {
|
||||
case MAC:
|
||||
outPath = jarPath.resolve("../Java.runtime/Contents/Home").normalize();
|
||||
break;
|
||||
default:
|
||||
outPath = jarPath.resolve("../runtime").normalize();
|
||||
}
|
||||
log.info("Assuming output path: {}", outPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
private JLink calculateToolPaths(Path javaHome) throws IOException {
|
||||
if(javaHome == null) {
|
||||
javaHome = Paths.get(System.getProperty("java.home"));
|
||||
}
|
||||
log.info("Using JAVA_HOME: {}", javaHome);
|
||||
jdepsPath = javaHome.resolve("bin").resolve(SystemUtilities.isWindows() ? "jdeps.exe" : "jdeps").normalize();
|
||||
jlinkPath = javaHome.resolve("bin").resolve(SystemUtilities.isWindows() ? "jlink.exe" : "jlink").normalize();
|
||||
log.info("Assuming jdeps path: {}", jdepsPath);
|
||||
log.info("Assuming jlink path: {}", jlinkPath);
|
||||
jdepsPath.toFile().setExecutable(true, false);
|
||||
jlinkPath.toFile().setExecutable(true, false);
|
||||
jdepsVersion = SystemUtilities.getJavaVersion(jdepsPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
private JLink calculateDepList() throws IOException {
|
||||
log.info("Calling jdeps to determine runtime dependencies");
|
||||
depList = new LinkedHashSet<>();
|
||||
|
||||
// JDK11.0.11+requires suppressing of missing deps
|
||||
String raw = jdepsVersion.compareTo(Version.valueOf("11.0.10")) > 0 ?
|
||||
ShellUtilities.executeRaw(jdepsPath.toString(), "--multi-release", "9", "--list-deps", "--ignore-missing-deps", jarPath.toString()) :
|
||||
ShellUtilities.executeRaw(jdepsPath.toString(), "--multi-release", "9", "--list-deps", jarPath.toString());
|
||||
if (raw == null || raw.trim().isEmpty() || raw.trim().startsWith("Warning") ) {
|
||||
throw new IOException("An unexpected error occurred calling jdeps. Please check the logs for details.\n" + raw);
|
||||
}
|
||||
for(String item : raw.split("\\r?\\n")) {
|
||||
item = item.trim();
|
||||
if(!item.isEmpty()) {
|
||||
if(item.startsWith("JDK") || item.startsWith("jdk8internals")) {
|
||||
// Remove e.g. "JDK removed internal API/sun.reflect"
|
||||
log.trace("Removing dependency: '{}'", item);
|
||||
continue;
|
||||
}
|
||||
if(item.contains("/")) {
|
||||
// Isolate base name e.g. "java.base/com.sun.net.ssl"
|
||||
item = item.split("/")[0];
|
||||
}
|
||||
depList.add(item);
|
||||
}
|
||||
}
|
||||
switch(targetPlatform) {
|
||||
case WINDOWS:
|
||||
// Java accessibility bridge dependency, see https://github.com/qzind/tray/issues/1234
|
||||
depList.add("jdk.accessibility");
|
||||
default:
|
||||
// Adds "bin/jcmd"
|
||||
depList.add("jdk.jcmd");
|
||||
// "jar:" URLs create transient zipfs dependency, see https://stackoverflow.com/a/57846672/3196753
|
||||
depList.add("jdk.zipfs");
|
||||
// fix for https://github.com/qzind/tray/issues/894 solution from https://github.com/adoptium/adoptium-support/issues/397
|
||||
depList.add("jdk.crypto.ec");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private JLink deployJre() throws IOException {
|
||||
if(targetPlatform == Platform.MAC) {
|
||||
// Deploy Contents/MacOS/libjli.dylib
|
||||
Path macOS = Files.createDirectories(outPath.resolve("../MacOS").normalize());
|
||||
Path jliLib = macOS.resolve("libjli.dylib");
|
||||
log.info("Deploying {}", macOS);
|
||||
try {
|
||||
// Not all jdks use a bundle format, but try this first
|
||||
Files.copy(jmodsPath.resolve("../../MacOS/libjli.dylib").normalize(), jliLib, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch(IOException ignore) {
|
||||
// Fallback to flat format
|
||||
String libjli = "../lib/jli/libjli.dylib";
|
||||
if(javaSemver.getMajorVersion() >= 21) {
|
||||
libjli = "../lib/libjli.dylib";
|
||||
}
|
||||
Files.copy(jmodsPath.resolve(libjli).normalize(), jliLib, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
// Deploy Contents/Info.plist
|
||||
HashMap<String, String> fieldMap = new HashMap<>();
|
||||
fieldMap.put("%BUNDLE_ID%", MacUtilities.getBundleId() + ".jre"); // e.g. io.qz.qz-tray.jre
|
||||
fieldMap.put("%BUNDLE_VERSION%", String.format("%s.%s.%s", javaSemver.getMajorVersion(), javaSemver.getMinorVersion(), javaSemver.getPatchVersion()));
|
||||
fieldMap.put("%BUNDLE_VERSION_FULL%", javaSemver.toString());
|
||||
fieldMap.put("%BUNDLE_VENDOR%", javaVendor.getVendorName());
|
||||
fieldMap.put("%BUNDLE_PRODUCT%", javaVendor.getProductName());
|
||||
log.info("Deploying {}/Info.plist", macOS.getParent());
|
||||
FileUtilities.configureAssetFile("assets/mac-runtime.plist.in", macOS.getParent().resolve("Info.plist"), fieldMap, JLink.class);
|
||||
}
|
||||
|
||||
FileUtils.deleteQuietly(outPath.toFile());
|
||||
|
||||
if(ShellUtilities.execute(jlinkPath.toString(),
|
||||
"--strip-debug",
|
||||
"--compress=2",
|
||||
"--no-header-files",
|
||||
"--no-man-pages",
|
||||
"--exclude-files=glob:**/legal/**",
|
||||
"--module-path", jmodsPath.toString(),
|
||||
"--add-modules", String.join(",", depList),
|
||||
"--output", outPath.toString())) {
|
||||
log.info("Successfully deployed a jre to {}", outPath);
|
||||
|
||||
// Remove all but java/javaw
|
||||
List<String> keepFiles = new ArrayList<>();
|
||||
//String[] keepFiles;
|
||||
String keepExt;
|
||||
switch(targetPlatform) {
|
||||
case WINDOWS:
|
||||
keepFiles.add("java.exe");
|
||||
keepFiles.add("javaw.exe");
|
||||
keepFiles.add("jcmd.exe");
|
||||
if(depList.contains("jdk.accessibility")) {
|
||||
// Java accessibility bridge switching tool
|
||||
keepFiles.add("jabswitch.exe");
|
||||
}
|
||||
// Windows stores ".dll" files in bin
|
||||
keepExt = ".dll";
|
||||
break;
|
||||
default:
|
||||
keepFiles.add("java");
|
||||
keepFiles.add("jcmd");
|
||||
keepExt = null;
|
||||
}
|
||||
|
||||
Files.list(outPath.resolve("bin")).forEach(binFile -> {
|
||||
if(Files.isDirectory(binFile) || (keepExt != null && binFile.toString().endsWith(keepExt))) {
|
||||
log.info("Keeping {}", binFile);
|
||||
return; // iterate forEach
|
||||
}
|
||||
for(String name : keepFiles) {
|
||||
if (binFile.endsWith(name)) {
|
||||
log.info("Keeping {}", binFile);
|
||||
return; // iterate forEach
|
||||
}
|
||||
}
|
||||
log.info("Deleting {}", binFile);
|
||||
binFile.toFile().delete();
|
||||
});
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
throw new IOException("An error occurred deploying the jre. Please check the logs for details.");
|
||||
}
|
||||
|
||||
public static String getParam(String paramName, String value, String fallback) {
|
||||
if(value != null && !value.isEmpty() && !value.trim().isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
log.info("No {} specified, assuming '{}'", paramName, fallback);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
45
old code/tray/src/qz/build/assets/mac-runtime.plist.in
Executable file
45
old code/tray/src/qz/build/assets/mac-runtime.plist.in
Executable file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>libjli.dylib</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>%BUNDLE_VENDOR% %BUNDLE_PRODUCT% %BUNDLE_VERSION_FULL%</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>%BUNDLE_ID%</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>7.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Java Runtime Image</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>%BUNDLE_VERSION%</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>%BUNDLE_VERSION%</string>
|
||||
<key>JavaVM</key>
|
||||
<dict>
|
||||
<key>JVMCapabilities</key>
|
||||
<array>
|
||||
<string>CommandLine</string>
|
||||
<string>JNI</string>
|
||||
<string>BundledApp</string>
|
||||
</array>
|
||||
<key>JVMMinimumFrameworkVersion</key>
|
||||
<string>17.0.0</string>
|
||||
<key>JVMMinimumSystemVersion</key>
|
||||
<string>10.6.0</string>
|
||||
<key>JVMPlatformVersion</key>
|
||||
<string>%BUNDLE_VERSION_FULL%</string>
|
||||
<key>JVMVendor</key>
|
||||
<string>%BUNDLE_VENDOR%</string>
|
||||
<key>JVMVersion</key>
|
||||
<string>%BUNDLE_VERSION%</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
0
old code/tray/src/qz/build/jlink/Arch.java
Executable file
0
old code/tray/src/qz/build/jlink/Arch.java
Executable file
68
old code/tray/src/qz/build/jlink/Parsable.java
Executable file
68
old code/tray/src/qz/build/jlink/Parsable.java
Executable file
@@ -0,0 +1,68 @@
|
||||
package qz.build.jlink;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A special template class for handling enums with varargs needing string matches.
|
||||
*
|
||||
* Parsable enums must declare <code>public static void String[] matches;</code>
|
||||
* in the constructor, which <code>parse(Class enumType, </T>String value) will
|
||||
* call using reflection.
|
||||
*
|
||||
* Enums are inherently static in Java and cannot extend superclasses. The
|
||||
* workaround to avoid code duplication is to leverage reflection and generics in
|
||||
* static utility functions.
|
||||
*
|
||||
* The downsides of this are:
|
||||
* - Reflection is slow
|
||||
* - Static helpers must be explicitly class-type-aware*
|
||||
*
|
||||
* *Non-static methods may be implicit, but create anti-patterns for static helpers
|
||||
* such as <code>parse(String value)</code> as they would exist at
|
||||
* <code>ENUM_ENTRY.parse(...)</code> rather than <code>EnumClass.parse(...)</code>.
|
||||
*/
|
||||
public interface Parsable<T extends Enum> {
|
||||
Logger log = LogManager.getLogger(Parsable.class);
|
||||
|
||||
static <T extends Enum<T>> T parse(Class<T> enumType, String value) {
|
||||
if(value != null && !value.trim().isEmpty()) {
|
||||
for(T parsable : enumType.getEnumConstants()) {
|
||||
try {
|
||||
Field matchesField = parsable.getClass().getDeclaredField("matches");
|
||||
String[] matches = (String[])matchesField.get(parsable);
|
||||
for(String match : matches) {
|
||||
if (match.equalsIgnoreCase(value)) {
|
||||
return parsable;
|
||||
}
|
||||
}
|
||||
} catch(NoSuchFieldException | IllegalAccessException | ClassCastException e) {
|
||||
log.warn("Parsable enums must have a 'public String[] matches' field", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.warn("Could not parse {} as a valid {} value", value, enumType.getSimpleName());
|
||||
return null;
|
||||
}
|
||||
|
||||
static <T extends Enum<T>> T parse(Class<T> enumType, String value, T fallback, boolean silent) {
|
||||
if(value != null && !value.trim().isEmpty()) {
|
||||
return parse(enumType, value);
|
||||
}
|
||||
if(!silent) {
|
||||
log.warn("No {} specified, assuming '{}'", enumType.getSimpleName(), ((Parsable)fallback).value());
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static <T extends Enum<T>> T parse(Class<T> enumType, String value, T fallback) {
|
||||
return parse(enumType, value, fallback, false);
|
||||
}
|
||||
|
||||
default String value() {
|
||||
return ((T)this).toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
37
old code/tray/src/qz/build/jlink/Platform.java
Executable file
37
old code/tray/src/qz/build/jlink/Platform.java
Executable file
@@ -0,0 +1,37 @@
|
||||
package qz.build.jlink;
|
||||
|
||||
|
||||
import qz.utils.SystemUtilities;
|
||||
|
||||
/**
|
||||
* Handling of platform names as they would appear in a URL
|
||||
* Values added must also be added to <code>ArgValue.JLINK --platform</code> values
|
||||
*/
|
||||
public enum Platform implements Parsable {
|
||||
MAC("mac"),
|
||||
WINDOWS("windows"),
|
||||
LINUX("linux");
|
||||
|
||||
public final String[] matches;
|
||||
Platform(String ... matches) { this.matches = matches; }
|
||||
|
||||
public static Platform parse(String value, Platform fallback) {
|
||||
return Parsable.parse(Platform.class, value, fallback);
|
||||
}
|
||||
|
||||
public static Platform parse(String value) {
|
||||
return Parsable.parse(Platform.class, value);
|
||||
}
|
||||
|
||||
public static Platform getCurrentPlatform() {
|
||||
switch(SystemUtilities.getOs()) {
|
||||
case MAC:
|
||||
return Platform.MAC;
|
||||
case WINDOWS:
|
||||
return Platform.WINDOWS;
|
||||
case LINUX:
|
||||
default:
|
||||
return Platform.LINUX;
|
||||
}
|
||||
}
|
||||
}
|
||||
72
old code/tray/src/qz/build/jlink/Url.java
Executable file
72
old code/tray/src/qz/build/jlink/Url.java
Executable file
@@ -0,0 +1,72 @@
|
||||
package qz.build.jlink;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.build.provision.params.Arch;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static qz.build.jlink.Vendor.*;
|
||||
|
||||
/**
|
||||
* Each JDK provider uses their own url format
|
||||
*/
|
||||
public class Url {
|
||||
static HashMap<Vendor, String> VENDOR_URL_MAP = new HashMap<>();
|
||||
static {
|
||||
VENDOR_URL_MAP.put(BELLSOFT, "https://download.bell-sw.com/java/%s/bellsoft-jdk%s-%s-%s.%s");
|
||||
VENDOR_URL_MAP.put(ECLIPSE, "https://github.com/adoptium/temurin%s-binaries/releases/download/jdk-%s/OpenJDK%sU-jdk_%s_%s_%s_%s.%s");
|
||||
VENDOR_URL_MAP.put(IBM, "https://github.com/ibmruntimes/semeru%s-binaries/releases/download/jdk-%s_%s-%s/ibm-semeru-open-jdk_%s_%s_%s_%s-%s.%s");
|
||||
VENDOR_URL_MAP.put(MICROSOFT, "https://aka.ms/download-jdk/microsoft-jdk-%s-%s-%s.%s");
|
||||
VENDOR_URL_MAP.put(AMAZON, "https://corretto.aws/downloads/resources/%s/amazon-corretto-%s-%s-%s.%s");
|
||||
VENDOR_URL_MAP.put(AZUL, "https://cdn.azul.com/zulu%s/bin/zulu%s-ca-jdk%s-%s_%s.%s");
|
||||
}
|
||||
|
||||
private static final Logger log = LogManager.getLogger(Url.class);
|
||||
|
||||
Vendor vendor;
|
||||
String pattern;
|
||||
public Url(Vendor vendor) {
|
||||
this.vendor = vendor;
|
||||
if(!VENDOR_URL_MAP.containsKey(vendor)) {
|
||||
throw new UnsupportedOperationException(String.format("Vendor provided '%s' couldn't be matched to a URL pattern, aborting.", vendor));
|
||||
}
|
||||
pattern = VENDOR_URL_MAP.get(vendor);
|
||||
}
|
||||
|
||||
public String format(Arch arch, Platform platform, String gcEngine, Version javaSemver, String javaVersion, String gcVer) throws UnsupportedEncodingException {
|
||||
Url pattern = new Url(vendor);
|
||||
String urlArch = vendor.getUrlArch(arch);
|
||||
String fileExt = vendor.getUrlExtension(platform);
|
||||
String urlPlatform = vendor.getUrlPlatform(platform);
|
||||
String urlJavaVersion = vendor.getUrlJavaVersion(javaSemver);
|
||||
|
||||
// Convert "+" to "%2B"
|
||||
String urlJavaVersionEncode = URLEncoder.encode(javaVersion, "UTF-8");
|
||||
|
||||
int javaMajor = javaSemver.getMajorVersion();
|
||||
|
||||
switch(vendor) {
|
||||
case BELLSOFT:
|
||||
return String.format(pattern.pattern, urlJavaVersionEncode, urlJavaVersionEncode, urlPlatform, urlArch, fileExt);
|
||||
case ECLIPSE:
|
||||
return String.format(pattern.pattern, javaMajor, urlJavaVersionEncode, javaMajor, urlArch, urlPlatform, gcEngine, urlJavaVersion, fileExt);
|
||||
case IBM:
|
||||
return String.format(pattern.pattern, javaMajor, urlJavaVersionEncode, gcEngine, gcVer, urlArch, urlPlatform, urlJavaVersion, gcEngine, gcVer, fileExt);
|
||||
case MICROSOFT:
|
||||
return String.format(pattern.pattern, urlJavaVersion, urlPlatform, urlArch, fileExt);
|
||||
case AMAZON:
|
||||
return String.format(pattern.pattern, urlJavaVersion, urlJavaVersion, urlPlatform, urlArch, fileExt);
|
||||
case AZUL:
|
||||
// Special handling of Linux aarch64
|
||||
String embedded = platform == Platform.LINUX ? "-embedded" : "";
|
||||
return String.format(pattern.pattern, embedded, gcVer, urlJavaVersion, urlPlatform, urlArch, fileExt);
|
||||
default:
|
||||
throw new UnsupportedOperationException(String.format("URL pattern for '%s' (%s) is missing a format implementation.", vendor, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
155
old code/tray/src/qz/build/jlink/Vendor.java
Executable file
155
old code/tray/src/qz/build/jlink/Vendor.java
Executable file
@@ -0,0 +1,155 @@
|
||||
package qz.build.jlink;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import qz.build.provision.params.Arch;
|
||||
|
||||
/**
|
||||
* Handling of java vendors
|
||||
*/
|
||||
public enum Vendor implements Parsable {
|
||||
ECLIPSE("Eclipse", "Adoptium", "adoptium", "temurin", "adoptopenjdk"),
|
||||
BELLSOFT("BellSoft", "Liberica", "bellsoft", "liberica"),
|
||||
IBM("IBM", "Semeru", "ibm", "semeru"),
|
||||
MICROSOFT("Microsoft", "OpenJDK", "microsoft"),
|
||||
AMAZON("Amazon", "Corretto", "amazon", "corretto"),
|
||||
AZUL("Azul", "Zulu", "azul", "zulu");
|
||||
|
||||
public String vendorName;
|
||||
public String productName;
|
||||
public final String[] matches;
|
||||
Vendor(String vendorName, String productName, String ... matches) {
|
||||
this.matches = matches;
|
||||
this.vendorName = vendorName;
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public static Vendor parse(String value, Vendor fallback) {
|
||||
return Parsable.parse(Vendor.class, value, fallback, true);
|
||||
}
|
||||
|
||||
public static Vendor parse(String value) {
|
||||
return Parsable.parse(Vendor.class, value);
|
||||
}
|
||||
|
||||
public String getVendorName() {
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Vendor to Arch value
|
||||
*/
|
||||
public String getUrlArch(Arch arch) {
|
||||
switch(arch) {
|
||||
case AARCH64:
|
||||
// All vendors seem to use "aarch64" universally
|
||||
return "aarch64";
|
||||
case ARM32:
|
||||
switch(this) {
|
||||
case BELLSOFT:
|
||||
return "arm32-vfp-hflt";
|
||||
case AZUL:
|
||||
return "aarch32hf";
|
||||
case MICROSOFT:
|
||||
case IBM:
|
||||
throw new UnsupportedOperationException("Vendor does not provide builds for this architecture");
|
||||
case AMAZON:
|
||||
case ECLIPSE:
|
||||
default:
|
||||
return "arm";
|
||||
}
|
||||
case RISCV64:
|
||||
return "riscv64";
|
||||
case X86:
|
||||
switch(this) {
|
||||
case AZUL:
|
||||
return "i686";
|
||||
case BELLSOFT:
|
||||
return "i586";
|
||||
case ECLIPSE:
|
||||
case IBM:
|
||||
return "x86-32";
|
||||
case AMAZON:
|
||||
default:
|
||||
return "x86";
|
||||
}
|
||||
case X86_64:
|
||||
default:
|
||||
switch(this) {
|
||||
// BellSoft uses "amd64"
|
||||
case BELLSOFT:
|
||||
return "amd64";
|
||||
}
|
||||
return "x64";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Vendor to Platform name
|
||||
*/
|
||||
public String getUrlPlatform(Platform platform) {
|
||||
switch(platform) {
|
||||
case MAC:
|
||||
switch(this) {
|
||||
case BELLSOFT:
|
||||
return "macos";
|
||||
case MICROSOFT:
|
||||
return "macOS";
|
||||
case AMAZON:
|
||||
case AZUL:
|
||||
return "macosx";
|
||||
}
|
||||
default:
|
||||
return platform.value();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Vendor and Platform to file extension
|
||||
*/
|
||||
public String getUrlExtension(Platform platform) {
|
||||
switch(this) {
|
||||
case BELLSOFT:
|
||||
switch(platform) {
|
||||
case LINUX:
|
||||
return "tar.gz";
|
||||
default:
|
||||
// BellSoft uses "zip" for mac and windows platforms
|
||||
return "zip";
|
||||
}
|
||||
default:
|
||||
switch(platform) {
|
||||
case WINDOWS:
|
||||
return "zip";
|
||||
default:
|
||||
return "tar.gz";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getUrlJavaVersion(Version javaSemver) {
|
||||
switch(this) {
|
||||
case MICROSOFT:
|
||||
case AZUL:
|
||||
// Return shorted version (Microsoft, Azul suppresses the build information from URLs)
|
||||
return javaSemver.toString().split("\\+")[0];
|
||||
case AMAZON:
|
||||
// Return lengthened version (Corretto formats major.minor.patch.build.number, e.g. 11.0.17.8.1)
|
||||
String[] parts = javaSemver.toString().split("\\+");
|
||||
String javaVersion = parts[0];
|
||||
//
|
||||
String buildAndNumber = parts[1];
|
||||
if(!buildAndNumber.contains(".")) {
|
||||
// Append ".1" if ".number" is missing
|
||||
buildAndNumber += ".1";
|
||||
}
|
||||
return String.format("%s.%s", javaVersion, buildAndNumber);
|
||||
}
|
||||
// All others seem to prefer "+" replaced with "_"
|
||||
return javaSemver.toString().replaceAll("\\+", "_");
|
||||
}
|
||||
}
|
||||
|
||||
328
old code/tray/src/qz/build/provision/ProvisionBuilder.java
Executable file
328
old code/tray/src/qz/build/provision/ProvisionBuilder.java
Executable file
@@ -0,0 +1,328 @@
|
||||
package qz.build.provision;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import qz.build.provision.params.Arch;
|
||||
import qz.build.provision.params.Os;
|
||||
import qz.build.provision.params.Phase;
|
||||
import qz.build.provision.params.Type;
|
||||
import qz.common.Constants;
|
||||
import qz.installer.provision.invoker.PropertyInvoker;
|
||||
import qz.utils.ArgValue;
|
||||
import qz.utils.SystemUtilities;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ProvisionBuilder {
|
||||
protected static final Logger log = LogManager.getLogger(ProvisionBuilder.class);
|
||||
|
||||
public static final Path BUILD_PROVISION_FOLDER = SystemUtilities.getJarParentPath().resolve(Constants.PROVISION_DIR);
|
||||
public static final File BUILD_PROVISION_FILE = BUILD_PROVISION_FOLDER.resolve(Constants.PROVISION_FILE).toFile();
|
||||
|
||||
private File ingestFile;
|
||||
private JSONArray jsonSteps;
|
||||
private Arch targetArch;
|
||||
private Os targetOs;
|
||||
|
||||
/**
|
||||
* Parses command line input to create a "provision" folder in the dist directory for customizing the installation or startup
|
||||
*/
|
||||
public ProvisionBuilder(String type, String phase, String os, String arch, String data, String args, String description, String ... varArgs) throws IOException, JSONException {
|
||||
createProvisionDirectory(false);
|
||||
|
||||
targetOs = Os.ALL;
|
||||
targetArch = Arch.ALL;
|
||||
jsonSteps = new JSONArray();
|
||||
|
||||
// Wrap into JSON so that we can save it
|
||||
JSONObject jsonStep = new JSONObject();
|
||||
putPattern(jsonStep, "description", description);
|
||||
putPattern(jsonStep, "type", type);
|
||||
putPattern(jsonStep, "phase", phase);
|
||||
putPattern(jsonStep, "os", os);
|
||||
putPattern(jsonStep, "arch", arch);
|
||||
putPattern(jsonStep, "data", data);
|
||||
putPattern(jsonStep, "args", args);
|
||||
putPattern(jsonStep, "arg%d", varArgs);
|
||||
|
||||
// Command line invocation, use the working directory
|
||||
Path relativePath = Paths.get(System.getProperty("user.dir"));
|
||||
ingestStep(jsonStep, relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by ant's <code>provision</code> target
|
||||
*/
|
||||
public ProvisionBuilder(File antJsonFile, String antTargetOs, String antTargetArch) throws IOException, JSONException {
|
||||
createProvisionDirectory(true);
|
||||
|
||||
// Calculate the target os, architecture
|
||||
this.targetArch = Arch.parseStrict(antTargetArch);
|
||||
this.targetOs = Os.parseStrict(antTargetOs);
|
||||
|
||||
this.jsonSteps = new JSONArray();
|
||||
this.ingestFile = antJsonFile;
|
||||
|
||||
String jsonData = FileUtils.readFileToString(antJsonFile, StandardCharsets.UTF_8);
|
||||
JSONArray pendingSteps = new JSONArray(jsonData);
|
||||
|
||||
// Cycle through so that each Step can be individually processed
|
||||
Path relativePath = antJsonFile.toPath().getParent();
|
||||
for(int i = 0; i < pendingSteps.length(); i++) {
|
||||
JSONObject jsonStep = pendingSteps.getJSONObject(i);
|
||||
System.out.println();
|
||||
try {
|
||||
ingestStep(jsonStep, relativePath);
|
||||
} catch(Exception e) {
|
||||
log.warn("[SKIPPED] Step '{}'", jsonStep, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public JSONArray getJson() {
|
||||
return jsonSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct as a Step to perform basic parsing/sanity checks
|
||||
* Copy resources (if needed) to provisioning directory
|
||||
*/
|
||||
private void ingestStep(JSONObject jsonStep, Path relativePath) throws JSONException, IOException {
|
||||
Step step = Step.parse(jsonStep, relativePath);
|
||||
if(!targetOs.matches(step.os)) {
|
||||
log.info("[SKIPPED] Os '{}' does not match target Os '{}' '{}'", Os.serialize(step.os), targetOs, step);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!targetArch.matches(step.arch)) {
|
||||
log.info("[SKIPPED] Arch '{}' does not match target Os '{}' '{}'", Arch.serialize(step.arch), targetArch, step);
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject any special inferences (such as inferring resources from args)
|
||||
inferAdditionalSteps(step);
|
||||
|
||||
if(copyResource(step)) {
|
||||
log.info("[SUCCESS] Step successfully processed '{}'", step);
|
||||
jsonSteps.put(step.toJSON());
|
||||
// Special case for custom websocket ports
|
||||
if(step.getType() == Type.PROPERTY && step.getPhase() == Phase.CERTGEN) {
|
||||
HashMap<String, String> pairs = PropertyInvoker.parsePropertyPairs(step);
|
||||
if(pairs.get(ArgValue.WEBSOCKET_SECURE_PORTS.getMatch()) != null ||
|
||||
pairs.get(ArgValue.WEBSOCKET_INSECURE_PORTS.getMatch()) != null) {
|
||||
// Clone to install step
|
||||
jsonSteps.put(step.cloneTo(Phase.INSTALL).toJSON());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.error("[SKIPPED] Resources could not be saved '{}'", step);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any resources files required for INSTALL and SCRIPT steps to provision folder
|
||||
*/
|
||||
public boolean copyResource(Step step) throws IOException {
|
||||
switch(step.getType()) {
|
||||
case CA:
|
||||
case CERT:
|
||||
case SCRIPT:
|
||||
case RESOURCE:
|
||||
case SOFTWARE:
|
||||
boolean isRelative = !Paths.get(step.getData()).isAbsolute();
|
||||
File src;
|
||||
if(isRelative) {
|
||||
if(ingestFile != null) {
|
||||
Path parentDir = ingestFile.getParentFile().toPath();
|
||||
src = parentDir.resolve(step.getData()).toFile();
|
||||
} else {
|
||||
throw formatted("Unable to resolve path: '%s' '%s'", step.getData(), step);
|
||||
}
|
||||
} else {
|
||||
src = new File(step.getData());
|
||||
}
|
||||
String fileName = src.getName();
|
||||
if(fileName.equals(BUILD_PROVISION_FILE.getName())) {
|
||||
throw formatted("Resource name conflicts with provision file '%s' '%s'", fileName, step);
|
||||
}
|
||||
File dest = BUILD_PROVISION_FOLDER.resolve(fileName).toFile();
|
||||
int i = 0;
|
||||
// Avoid conflicting file names
|
||||
String name = dest.getName();
|
||||
|
||||
// Avoid resource clobbering when being invoked by command line or providing certificates.
|
||||
// Otherwise, assume the intent is to re-use the same resource (e.g. "my_script.sh", etc)
|
||||
if(ingestFile == null || step.getType() == Type.CERT) {
|
||||
while(dest.exists()) {
|
||||
// Append "filename-1.txt" until there's no longer a conflict
|
||||
if (name.contains(".")) {
|
||||
dest = BUILD_PROVISION_FOLDER.resolve(String.format("%s-%s.%s", FilenameUtils.removeExtension(name), ++i,
|
||||
FilenameUtils.getExtension(name))).toFile();
|
||||
} else {
|
||||
dest = BUILD_PROVISION_FOLDER.resolve(String.format("%-%", name, ++i)).toFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileUtils.copyFile(src, dest);
|
||||
if(dest.exists()) {
|
||||
step.setData(BUILD_PROVISION_FOLDER.relativize(dest.toPath()).toString());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the JSONObject to the end of the provisionFile
|
||||
*/
|
||||
public boolean saveJson(boolean overwrite) throws IOException, JSONException {
|
||||
// Read existing JSON file if exists
|
||||
JSONArray mergeSteps;
|
||||
if(!overwrite && BUILD_PROVISION_FILE.exists()) {
|
||||
String jsonData = FileUtils.readFileToString(BUILD_PROVISION_FILE, StandardCharsets.UTF_8);
|
||||
mergeSteps = new JSONArray(jsonData);
|
||||
} else {
|
||||
mergeSteps = new JSONArray();
|
||||
}
|
||||
|
||||
// Merge in new steps
|
||||
for(int i = 0; i < jsonSteps.length(); i++) {
|
||||
mergeSteps.put(jsonSteps.getJSONObject(i));
|
||||
}
|
||||
|
||||
FileUtils.writeStringToFile(BUILD_PROVISION_FILE, mergeSteps.toString(3), StandardCharsets.UTF_8);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for adding a name/value pair into the JSONObject
|
||||
*/
|
||||
private static void putPattern(JSONObject jsonStep, String name, String val) throws JSONException {
|
||||
if(val != null && !val.isEmpty()) {
|
||||
jsonStep.put(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for adding consecutive patterned value pairs into the JSONObject
|
||||
* e.g. --arg1 "foo" --arg2 "bar"
|
||||
*/
|
||||
private static void putPattern(JSONObject jsonStep, String pattern, String ... varArgs) throws JSONException {
|
||||
int argCounter = 0;
|
||||
for(String arg : varArgs) {
|
||||
jsonStep.put(String.format(pattern, ++argCounter), arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createProvisionDirectory(boolean cleanDirectory) throws IOException {
|
||||
if(cleanDirectory) {
|
||||
FileUtils.deleteDirectory(BUILD_PROVISION_FOLDER.toFile());
|
||||
}
|
||||
if(BUILD_PROVISION_FOLDER.toFile().isDirectory()) {
|
||||
return;
|
||||
}
|
||||
if(BUILD_PROVISION_FOLDER.toFile().mkdirs()) {
|
||||
return;
|
||||
}
|
||||
throw formatted("Could not create provision destination: '%'", BUILD_PROVISION_FOLDER);
|
||||
}
|
||||
|
||||
private static IOException formatted(String message, Object ... args) {
|
||||
String formatted = String.format(message, args);
|
||||
return new IOException(formatted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first index of the specified arg prefix pattern(s)
|
||||
*
|
||||
* e.g. if pattern is "/f1", it will return 1 from args { "/s", "/f1C:\foo" }
|
||||
*/
|
||||
private int argPrefixIndex(Step step, String ... prefixes) {
|
||||
for(int i = 0; i < step.args.size() ; i++){
|
||||
for(String prefix : prefixes) {
|
||||
if (step.args.get(i).toLowerCase().startsWith(prefix.toLowerCase())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "value" of the specified arg prefix pattern(s)
|
||||
*
|
||||
* e.g. if pattern is "/f1", it will return "C:\foo" from args { "/s", "/f1C:\foo" }
|
||||
*
|
||||
*/
|
||||
private String argPrefixValue(Step step, int index, String ... prefixes) {
|
||||
String arg = step.args.get(index);
|
||||
String value = null;
|
||||
for(String prefix : prefixes) {
|
||||
if (arg.toLowerCase().startsWith(prefix.toLowerCase())) {
|
||||
value = arg.substring(prefix.length());
|
||||
if((value.startsWith("\"") && value.endsWith("\"")) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
// Remove surrounding quotes
|
||||
value = value.substring(1, value.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the provided step into a new step that performs a prerequisite task.
|
||||
*
|
||||
* This is "magic" in the sense that it's highly specific to <code>Type</code>
|
||||
* <code>Os</code> and <code>Step.args</code>.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* Older InstallShield installers supported the <code>/f1</code> parameter which
|
||||
* implies an answer file of which we need to bundle for a successful deployment.
|
||||
*/
|
||||
private void inferAdditionalSteps(Step orig) throws JSONException, IOException {
|
||||
// Infer resource step for InstallShield .iss answer files
|
||||
if(orig.getType() == Type.SOFTWARE && Os.WINDOWS.matches(orig.getOs())) {
|
||||
String[] patterns = { "/f1", "-f1" };
|
||||
int index = argPrefixIndex(orig, patterns);
|
||||
if(index > 0) {
|
||||
String resource = argPrefixValue(orig, index, patterns);
|
||||
if(resource != null) {
|
||||
// Clone to copy the Phase, Os and Description
|
||||
Step step = orig.clone();
|
||||
|
||||
// Swap Type, clear args and update the data
|
||||
step.setType(Type.RESOURCE);
|
||||
step.setArgs(new ArrayList<>());
|
||||
step.setData(resource);
|
||||
|
||||
if(copyResource(step)) {
|
||||
File resourceFile = new File(resource);
|
||||
jsonSteps.put(step.toJSON());
|
||||
orig.getArgs().set(index, String.format("/f1\"%s\"", resourceFile.getName()));
|
||||
log.info("[SUCCESS] Step successfully inferred and appended '{}'", step);
|
||||
} else {
|
||||
log.error("[SKIPPED] Resources could not be saved '{}'", step);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
337
old code/tray/src/qz/build/provision/Step.java
Executable file
337
old code/tray/src/qz/build/provision/Step.java
Executable file
@@ -0,0 +1,337 @@
|
||||
package qz.build.provision;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import qz.build.provision.params.Arch;
|
||||
import qz.build.provision.params.Os;
|
||||
import qz.build.provision.params.Phase;
|
||||
import qz.build.provision.params.Type;
|
||||
import qz.build.provision.params.types.Remover;
|
||||
import qz.build.provision.params.types.Software;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class Step {
|
||||
protected static final Logger log = LogManager.getLogger(Step.class);
|
||||
|
||||
String description;
|
||||
Type type;
|
||||
List<String> args; // Type.SCRIPT or Type.INSTALLER or Type.CONF only
|
||||
HashSet<Os> os;
|
||||
HashSet<Arch> arch;
|
||||
Phase phase;
|
||||
String data;
|
||||
|
||||
Path relativePath;
|
||||
Class relativeClass;
|
||||
|
||||
public Step(Path relativePath, String description, Type type, HashSet<Os> os, HashSet<Arch> arch, Phase phase, String data, List<String> args) {
|
||||
this.relativePath = relativePath;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
this.os = os;
|
||||
this.arch = arch;
|
||||
this.phase = phase;
|
||||
this.data = data;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only should be used by unit tests
|
||||
*/
|
||||
Step(Class relativeClass, String description, Type type, HashSet<Os> os, HashSet<Arch> arch, Phase phase, String data, List<String> args) {
|
||||
this.relativeClass = relativeClass;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
this.os = os;
|
||||
this.arch = arch;
|
||||
this.phase = phase;
|
||||
this.data = data;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Step { " +
|
||||
"description=\"" + description + "\", " +
|
||||
"type=\"" + type + "\", " +
|
||||
"os=\"" + Os.serialize(os) + "\", " +
|
||||
"arch=\"" + Arch.serialize(arch) + "\", " +
|
||||
"phase=\"" + phase + "\", " +
|
||||
"data=\"" + data + "\", " +
|
||||
"args=\"" + StringUtils.join(args, ",") + "\" " +
|
||||
"}";
|
||||
}
|
||||
|
||||
public JSONObject toJSON() throws JSONException {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("description", description)
|
||||
.put("type", type)
|
||||
.put("os", Os.serialize(os))
|
||||
.put("arch", Arch.serialize(arch))
|
||||
.put("phase", phase)
|
||||
.put("data", data);
|
||||
|
||||
for(int i = 0; i < args.size(); i++) {
|
||||
json.put(String.format("arg%s", i + 1), args.get(i));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public void setArgs(List<String> args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public HashSet<Os> getOs() {
|
||||
return os;
|
||||
}
|
||||
|
||||
public void setOs(HashSet<Os> os) {
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public HashSet<Arch> getArch() {
|
||||
return arch;
|
||||
}
|
||||
|
||||
public void setArch(HashSet<Arch> arch) {
|
||||
this.arch = arch;
|
||||
}
|
||||
|
||||
public Phase getPhase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
public void setPhase(Phase phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Class getRelativeClass() {
|
||||
return relativeClass;
|
||||
}
|
||||
|
||||
public Path getRelativePath() {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
public boolean usingClass() {
|
||||
return relativeClass != null;
|
||||
}
|
||||
|
||||
public boolean usingPath() {
|
||||
return relativePath != null;
|
||||
}
|
||||
|
||||
public static Step parse(JSONObject jsonStep, Object relativeObject) {
|
||||
String description = jsonStep.optString("description", "");
|
||||
Type type = Type.parse(jsonStep.optString("type", null));
|
||||
String data = jsonStep.optString("data", null);
|
||||
|
||||
// Handle installer args
|
||||
List<String> args = new LinkedList<>();
|
||||
if(type == Type.SOFTWARE || type == Type.CONF) {
|
||||
// Handle space-delimited args
|
||||
args = Software.parseArgs(jsonStep.optString("args", ""));
|
||||
// Handle standalone single args (won't break on whitespace)
|
||||
// e.g. "arg1": "C:\Program Files\Foo"
|
||||
int argCounter = 0;
|
||||
while(true) {
|
||||
String singleArg = jsonStep.optString(String.format("arg%d", ++argCounter), "");
|
||||
if(!singleArg.trim().isEmpty()) {
|
||||
args.add(singleArg.trim());
|
||||
} else {
|
||||
// stop searching if the next incremental arg (e.g. "arg2") isn't found
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mandate "args" as the CONF path
|
||||
if(type == Type.CONF) {
|
||||
// Honor "path" first, if provided
|
||||
String path = jsonStep.optString("path", "");
|
||||
if(!path.isEmpty()) {
|
||||
args.add(0, path);
|
||||
}
|
||||
|
||||
// Keep only the first value
|
||||
if(args.size() > 0) {
|
||||
args = args.subList(0, 1);
|
||||
} else {
|
||||
throw formatted("Conf path value cannot be blank.");
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Os> os = new HashSet<>();
|
||||
if(jsonStep.has("os")) {
|
||||
// Do not tolerate bad os values
|
||||
String osString = jsonStep.optString("os");
|
||||
os = Os.parse(osString);
|
||||
if(os.size() == 0) {
|
||||
throw formatted("Os provided '%s' could not be parsed", osString);
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Arch> arch = new HashSet<>();
|
||||
if(jsonStep.has("arch")) {
|
||||
// Do not tolerate bad arch values
|
||||
String archString = jsonStep.optString("arch");
|
||||
arch = Arch.parse(archString);
|
||||
if(arch.size() == 0) {
|
||||
throw formatted("Arch provided \"%s\" could not be parsed", archString);
|
||||
}
|
||||
}
|
||||
|
||||
Phase phase = null;
|
||||
if(jsonStep.has("phase")) {
|
||||
String phaseString = jsonStep.optString("phase", null);
|
||||
phase = Phase.parse(phaseString);
|
||||
if(phase == null) {
|
||||
log.warn("Phase provided \"{}\" could not be parsed", phaseString);
|
||||
}
|
||||
}
|
||||
Step step;
|
||||
if(relativeObject instanceof Path) {
|
||||
step = new Step((Path)relativeObject, description, type, os, arch, phase, data, args);
|
||||
} else if(relativeObject instanceof Class) {
|
||||
step = new Step((Class)relativeObject, description, type, os, arch, phase, data, args);
|
||||
} else {
|
||||
throw formatted("Parameter relativeObject must be of type 'Path' or 'Class' but '%s' was provided", relativeObject.getClass());
|
||||
}
|
||||
return step.sanitize();
|
||||
}
|
||||
|
||||
private Step sanitize() {
|
||||
return throwIfNull("Type", type)
|
||||
.throwIfNull("Data", data)
|
||||
.validateOs()
|
||||
.validateArch()
|
||||
.enforcePhase(Type.PREFERENCE, Phase.STARTUP)
|
||||
.enforcePhase(Type.CA, Phase.CERTGEN)
|
||||
.enforcePhase(Type.CERT, Phase.STARTUP)
|
||||
.enforcePhase(Type.CONF, Phase.CERTGEN)
|
||||
.enforcePhase(Type.SOFTWARE, Phase.INSTALL)
|
||||
.enforcePhase(Type.REMOVER, Phase.INSTALL)
|
||||
.enforcePhase(Type.PROPERTY, Phase.CERTGEN, Phase.INSTALL)
|
||||
.validateRemover();
|
||||
}
|
||||
|
||||
private Step validateRemover() {
|
||||
if(type != Type.REMOVER) {
|
||||
return this;
|
||||
}
|
||||
Remover remover = Remover.parse(data);
|
||||
switch(remover) {
|
||||
case CUSTOM:
|
||||
break;
|
||||
default:
|
||||
if(remover.matchesCurrentSystem()) {
|
||||
throw formatted("Remover '%s' would conflict with this installer, skipping. ", remover);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Custom removers must have three elements
|
||||
if(data == null || data.split(",").length != 3) {
|
||||
throw formatted("Remover data '%s' is invalid. Data must match a known type [%s] or contain exactly 3 elements.", data, Remover.valuesDelimited(","));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Step throwIfNull(String name, Object value) {
|
||||
if(value == null) {
|
||||
throw formatted("%s cannot be null", name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Step validateOs() {
|
||||
if(os == null) {
|
||||
if(type == Type.SOFTWARE) {
|
||||
// Software must default to a sane operating system
|
||||
os = Software.parse(data).defaultOs();
|
||||
} else {
|
||||
os = new HashSet<>();
|
||||
}
|
||||
}
|
||||
if(os.size() == 0) {
|
||||
os.add(Os.ALL);
|
||||
log.debug("Os list is null, assuming '{}'", Os.ALL);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Step validateArch() {
|
||||
if(arch == null) {
|
||||
arch = new HashSet<>();
|
||||
}
|
||||
if(arch.size() == 0) {
|
||||
arch.add(Arch.ALL);
|
||||
log.debug("Arch list is null, assuming '{}'", Arch.ALL);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Step enforcePhase(Type matchType, Phase ... requiredPhases) {
|
||||
if(requiredPhases.length == 0) {
|
||||
throw new UnsupportedOperationException("At least one Phase must be specified");
|
||||
}
|
||||
if(type == matchType) {
|
||||
for(Phase requiredPhase : requiredPhases) {
|
||||
if (phase == null) {
|
||||
phase = requiredPhase;
|
||||
log.debug("Phase is null, defaulting to '{}' based on Type '{}'", phase, type);
|
||||
return this;
|
||||
} else if (phase == requiredPhase) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
log.debug("Phase '{}' is unsupported for Type '{}', defaulting to '{}'", phase, type, phase = requiredPhases[0]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException formatted(String message, Object ... args) {
|
||||
String formatted = String.format(message, args);
|
||||
return new UnsupportedOperationException(formatted);
|
||||
}
|
||||
Step cloneTo(Phase phase) {
|
||||
return relativePath != null ?
|
||||
new Step(relativePath, description, type, os, arch, phase, data, args) :
|
||||
new Step(relativeClass, description, type, os, arch, phase, data, args);
|
||||
}
|
||||
|
||||
public Step clone() {
|
||||
return cloneTo(this.phase);
|
||||
}
|
||||
}
|
||||
70
old code/tray/src/qz/build/provision/params/Arch.java
Executable file
70
old code/tray/src/qz/build/provision/params/Arch.java
Executable file
@@ -0,0 +1,70 @@
|
||||
package qz.build.provision.params;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Basic architecture parser
|
||||
*
|
||||
* Note: All aliases must be lowercase
|
||||
*/
|
||||
public enum Arch {
|
||||
X86("x32", "i386", "i486", "i586", "i686"),
|
||||
X86_64("amd64"),
|
||||
ARM32("arm", "armv1", "armv2", "armv3", "armv4", "armv5", "armv6", "armv7"),
|
||||
AARCH64("arm64", "armv8", "armv9"),
|
||||
RISCV32("rv32"),
|
||||
RISCV64("rv64"),
|
||||
PPC64("powerpc", "powerpc64"),
|
||||
ALL(), // special handling
|
||||
UNKNOWN();
|
||||
|
||||
private HashSet<String> aliases = new HashSet<>();
|
||||
Arch(String ... aliases) {
|
||||
this.aliases.add(name().toLowerCase(Locale.ENGLISH));
|
||||
this.aliases.addAll(Arrays.asList(aliases));
|
||||
}
|
||||
|
||||
public static Arch parseStrict(String input) throws UnsupportedOperationException {
|
||||
return EnumParser.parseStrict(Arch.class, input, ALL, UNKNOWN);
|
||||
}
|
||||
|
||||
public static HashSet<Arch> parse(String input) {
|
||||
return EnumParser.parseSet(Arch.class, Arch.ALL, input);
|
||||
}
|
||||
|
||||
public static Arch parse(String input, Arch fallback) {
|
||||
Arch found = bestMatch(input);
|
||||
return found == UNKNOWN ? fallback : found;
|
||||
}
|
||||
|
||||
public static Arch bestMatch(String input) {
|
||||
if(input != null) {
|
||||
for(Arch arch : values()) {
|
||||
if (arch.aliases.contains(input.toLowerCase())) {
|
||||
return arch;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Arch.UNKNOWN;
|
||||
}
|
||||
|
||||
public boolean matches(HashSet<Arch> archList) {
|
||||
return this == ALL || archList.contains(ALL) || (this != UNKNOWN && archList.contains(this));
|
||||
}
|
||||
|
||||
public static String serialize(HashSet<Arch> archList) {
|
||||
if(archList.contains(ALL)) {
|
||||
return "*";
|
||||
}
|
||||
return StringUtils.join(archList, "|");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
64
old code/tray/src/qz/build/provision/params/EnumParser.java
Executable file
64
old code/tray/src/qz/build/provision/params/EnumParser.java
Executable file
@@ -0,0 +1,64 @@
|
||||
package qz.build.provision.params;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
|
||||
public interface EnumParser {
|
||||
/**
|
||||
* Basic enum parser
|
||||
*/
|
||||
|
||||
static <T extends Enum<T>> T parse(Class<T> clazz, String s) {
|
||||
return parse(clazz, s, null);
|
||||
}
|
||||
|
||||
static <T extends Enum<T>> T parse(Class<T> clazz, String s, T fallbackValue) {
|
||||
if(s != null) {
|
||||
for(T en : EnumSet.allOf(clazz)) {
|
||||
if (en.name().equalsIgnoreCase(s)) {
|
||||
return en;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
static <T extends Enum<T>> T parseStrict(Class<T> clazz, String s, T ... blocklist) throws UnsupportedOperationException {
|
||||
if(s != null) {
|
||||
HashSet<T> matched = parseSet(clazz, null, s);
|
||||
if (matched.size() == 1) {
|
||||
T returnVal = matched.iterator().next();
|
||||
boolean blocked = false;
|
||||
for(T block : blocklist) {
|
||||
if(returnVal == block) {
|
||||
blocked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!blocked) {
|
||||
return returnVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException(String.format("%s value '%s' failed to match one and only one item", clazz.getSimpleName(), s));
|
||||
}
|
||||
|
||||
static <T extends Enum<T>> HashSet<T> parseSet(Class<T> clazz, T all, String s) {
|
||||
HashSet<T> matched = new HashSet<>();
|
||||
if(s != null) {
|
||||
// Handle ALL="*"
|
||||
if (all != null && s.equals("*")) {
|
||||
matched.add(all);
|
||||
}
|
||||
|
||||
String[] parts = s.split("\\|");
|
||||
for(String part : parts) {
|
||||
T parsed = parse(clazz, part);
|
||||
if (parsed != null) {
|
||||
matched.add(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
67
old code/tray/src/qz/build/provision/params/Os.java
Executable file
67
old code/tray/src/qz/build/provision/params/Os.java
Executable file
@@ -0,0 +1,67 @@
|
||||
package qz.build.provision.params;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import qz.utils.SystemUtilities;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Basic OS parser
|
||||
*/
|
||||
public enum Os {
|
||||
WINDOWS,
|
||||
MAC,
|
||||
LINUX,
|
||||
SOLARIS, // unsupported
|
||||
ALL, // special handling
|
||||
UNKNOWN;
|
||||
|
||||
public boolean matches(HashSet<Os> osList) {
|
||||
return this == ALL || osList.contains(ALL) || (this != UNKNOWN && osList.contains(this));
|
||||
}
|
||||
|
||||
public static boolean matchesHost(HashSet<Os> osList) {
|
||||
for(Os os : osList) {
|
||||
if(os == SystemUtilities.getOs() || os == ALL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Os parseStrict(String input) throws UnsupportedOperationException {
|
||||
return EnumParser.parseStrict(Os.class, input, ALL, UNKNOWN);
|
||||
}
|
||||
|
||||
public static Os bestMatch(String input) {
|
||||
if(input != null) {
|
||||
String name = input.toLowerCase(Locale.ENGLISH);
|
||||
if (name.contains("win")) {
|
||||
return Os.WINDOWS;
|
||||
} else if (name.contains("mac")) {
|
||||
return Os.MAC;
|
||||
} else if (name.contains("linux")) {
|
||||
return Os.LINUX;
|
||||
} else if (name.contains("sunos")) {
|
||||
return Os.SOLARIS;
|
||||
}
|
||||
}
|
||||
return Os.UNKNOWN;
|
||||
}
|
||||
|
||||
public static HashSet<Os> parse(String input) {
|
||||
return EnumParser.parseSet(Os.class, Os.ALL, input);
|
||||
}
|
||||
|
||||
public static String serialize(HashSet<Os> osList) {
|
||||
if(osList.contains(ALL)) {
|
||||
return "*";
|
||||
}
|
||||
return StringUtils.join(osList, "|");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
19
old code/tray/src/qz/build/provision/params/Phase.java
Executable file
19
old code/tray/src/qz/build/provision/params/Phase.java
Executable file
@@ -0,0 +1,19 @@
|
||||
package qz.build.provision.params;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum Phase {
|
||||
INSTALL,
|
||||
CERTGEN,
|
||||
STARTUP,
|
||||
UNINSTALL;
|
||||
|
||||
public static Phase parse(String input) {
|
||||
return EnumParser.parse(Phase.class, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
24
old code/tray/src/qz/build/provision/params/Type.java
Executable file
24
old code/tray/src/qz/build/provision/params/Type.java
Executable file
@@ -0,0 +1,24 @@
|
||||
package qz.build.provision.params;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum Type {
|
||||
SCRIPT,
|
||||
SOFTWARE,
|
||||
RESOURCE,
|
||||
REMOVER, // QZ Tray remover
|
||||
CA,
|
||||
CERT,
|
||||
CONF,
|
||||
PROPERTY,
|
||||
PREFERENCE;
|
||||
|
||||
public static Type parse(String input) {
|
||||
return EnumParser.parse(Type.class, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
67
old code/tray/src/qz/build/provision/params/types/Remover.java
Executable file
67
old code/tray/src/qz/build/provision/params/types/Remover.java
Executable file
@@ -0,0 +1,67 @@
|
||||
package qz.build.provision.params.types;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import qz.build.provision.params.EnumParser;
|
||||
import qz.common.Constants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public enum Remover {
|
||||
QZ("QZ Tray", "qz-tray", "qz"),
|
||||
CUSTOM(null, null, null); // reserved
|
||||
|
||||
private String aboutTitle;
|
||||
private String propsFile;
|
||||
private String dataDir;
|
||||
|
||||
Remover(String aboutTitle, String propsFile, String dataDir) {
|
||||
this.aboutTitle = aboutTitle;
|
||||
this.propsFile = propsFile;
|
||||
this.dataDir = dataDir;
|
||||
}
|
||||
|
||||
public String getAboutTitle() {
|
||||
return aboutTitle;
|
||||
}
|
||||
|
||||
public String getPropsFile() {
|
||||
return propsFile;
|
||||
}
|
||||
|
||||
public String getDataDir() {
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
public static String valuesDelimited(String delimiter) {
|
||||
ArrayList<Remover> listing = new ArrayList<>(Arrays.asList(values()));
|
||||
listing.remove(CUSTOM);
|
||||
return StringUtils.join(listing, delimiter).toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to custom if not found
|
||||
*/
|
||||
public static Remover parse(String input) {
|
||||
Remover remover = EnumParser.parse(Remover.class, input);
|
||||
if(remover == CUSTOM) {
|
||||
throw new UnsupportedOperationException("Remover 'custom' is reserved for internal purposes");
|
||||
}
|
||||
if(remover == null) {
|
||||
remover = CUSTOM;
|
||||
}
|
||||
return remover;
|
||||
}
|
||||
|
||||
public boolean matchesCurrentSystem() {
|
||||
return Constants.ABOUT_TITLE.equals(aboutTitle) ||
|
||||
Constants.PROPS_FILE.equals(propsFile) ||
|
||||
Constants.DATA_DIR.equals(dataDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
31
old code/tray/src/qz/build/provision/params/types/Script.java
Executable file
31
old code/tray/src/qz/build/provision/params/types/Script.java
Executable file
@@ -0,0 +1,31 @@
|
||||
package qz.build.provision.params.types;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import qz.build.provision.params.EnumParser;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
public enum Script {
|
||||
PS1,
|
||||
BAT,
|
||||
SH,
|
||||
PY,
|
||||
RB;
|
||||
|
||||
public static Script parse(String input) {
|
||||
if(input != null && !input.isEmpty()) {
|
||||
return EnumParser.parse(Script.class, FilenameUtils.getExtension(input), SH);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Script parse(Path path) {
|
||||
return parse(path.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
63
old code/tray/src/qz/build/provision/params/types/Software.java
Executable file
63
old code/tray/src/qz/build/provision/params/types/Software.java
Executable file
@@ -0,0 +1,63 @@
|
||||
package qz.build.provision.params.types;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import qz.build.provision.params.EnumParser;
|
||||
import qz.build.provision.params.Os;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public enum Software {
|
||||
EXE,
|
||||
MSI,
|
||||
PKG,
|
||||
DMG,
|
||||
RUN,
|
||||
UNKNOWN;
|
||||
|
||||
public static Software parse(String input) {
|
||||
return EnumParser.parse(Software.class, FilenameUtils.getExtension(input), UNKNOWN);
|
||||
}
|
||||
|
||||
public static Software parse(Path path) {
|
||||
return parse(path.toString());
|
||||
}
|
||||
|
||||
public static List<String> parseArgs(String input) {
|
||||
List<String> args = new LinkedList<>();
|
||||
if(input != null) {
|
||||
String[] parts = input.split(" ");
|
||||
for(String part : parts) {
|
||||
if(!part.trim().isEmpty()) {
|
||||
args.add(part.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public HashSet<Os> defaultOs() {
|
||||
HashSet<Os> list = new HashSet<>();
|
||||
switch(this) {
|
||||
case EXE:
|
||||
case MSI:
|
||||
list.add(Os.WINDOWS);
|
||||
break;
|
||||
case PKG:
|
||||
case DMG:
|
||||
list.add(Os.MAC);
|
||||
break;
|
||||
case RUN:
|
||||
list.add(Os.LINUX);
|
||||
break;
|
||||
default:
|
||||
list.add(Os.ALL);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
205
old code/tray/src/qz/common/AboutInfo.java
Executable file
205
old code/tray/src/qz/common/AboutInfo.java
Executable file
@@ -0,0 +1,205 @@
|
||||
package qz.common;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import org.apache.commons.lang3.time.DurationFormatUtils;
|
||||
import org.apache.commons.ssl.Base64;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.x509.extension.X509ExtensionUtil;
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.installer.certificate.KeyPairWrapper;
|
||||
import qz.installer.certificate.CertificateManager;
|
||||
import qz.utils.MacUtilities;
|
||||
import qz.utils.StringUtilities;
|
||||
import qz.utils.SystemUtilities;
|
||||
import qz.ws.PrintSocketServer;
|
||||
import qz.ws.WebsocketPorts;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
|
||||
public class AboutInfo {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(AboutInfo.class);
|
||||
|
||||
private static String preferredHostname = "localhost";
|
||||
|
||||
public static JSONObject gatherAbout(String domain, CertificateManager certificateManager) {
|
||||
JSONObject about = new JSONObject();
|
||||
|
||||
try {
|
||||
about.put("product", product());
|
||||
about.put("socket", socket(certificateManager, domain));
|
||||
about.put("environment", environment());
|
||||
about.put("ssl", ssl(certificateManager));
|
||||
about.put("libraries", libraries());
|
||||
about.put("charsets", charsets());
|
||||
}
|
||||
catch(JSONException | GeneralSecurityException e) {
|
||||
log.error("Failed to write JSON data", e);
|
||||
}
|
||||
|
||||
return about;
|
||||
}
|
||||
|
||||
private static JSONObject product() throws JSONException {
|
||||
JSONObject product = new JSONObject();
|
||||
|
||||
product
|
||||
.put("title", Constants.ABOUT_TITLE)
|
||||
.put("version", Constants.VERSION)
|
||||
.put("vendor", Constants.ABOUT_COMPANY)
|
||||
.put("url", Constants.ABOUT_URL);
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
private static JSONObject socket(CertificateManager certificateManager, String domain) throws JSONException {
|
||||
JSONObject socket = new JSONObject();
|
||||
String sanitizeDomain = StringUtilities.escapeHtmlEntities(domain);
|
||||
WebsocketPorts websocketPorts = PrintSocketServer.getWebsocketPorts();
|
||||
|
||||
// Gracefully handle XSS per https://github.com/qzind/tray/issues/1099
|
||||
if(sanitizeDomain.contains("<") || sanitizeDomain.contains(">")) {
|
||||
log.warn("Something smells fishy about this domain: \"{}\", skipping", domain);
|
||||
sanitizeDomain = "unknown";
|
||||
}
|
||||
|
||||
socket
|
||||
.put("domain", sanitizeDomain)
|
||||
.put("secureProtocol", "wss")
|
||||
.put("securePort", certificateManager.isSslActive() ? websocketPorts.getSecurePort() : "none")
|
||||
.put("insecureProtocol", "ws")
|
||||
.put("insecurePort", websocketPorts.getInsecurePort());
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
private static JSONObject environment() throws JSONException {
|
||||
JSONObject environment = new JSONObject();
|
||||
|
||||
long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
|
||||
|
||||
environment
|
||||
.put("os", SystemUtilities.getOsDisplayName())
|
||||
.put("os version", SystemUtilities.getOsDisplayVersion())
|
||||
.put("java", String.format("%s (%s)", Constants.JAVA_VERSION, SystemUtilities.getArch().toString().toLowerCase()))
|
||||
.put("java (location)", System.getProperty("java.home"))
|
||||
.put("java (vendor)", Constants.JAVA_VENDOR)
|
||||
.put("uptime", DurationFormatUtils.formatDurationWords(uptime, true, false))
|
||||
.put("uptimeMillis", uptime)
|
||||
.put("sandbox", SystemUtilities.isMac() && MacUtilities.isSandboxed());
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
private static JSONObject ssl(CertificateManager certificateManager) throws JSONException, CertificateEncodingException {
|
||||
JSONObject ssl = new JSONObject();
|
||||
|
||||
JSONArray certs = new JSONArray();
|
||||
|
||||
for (KeyPairWrapper keyPair : new KeyPairWrapper[]{certificateManager.getCaKeyPair(), certificateManager.getSslKeyPair() }) {
|
||||
X509Certificate x509 = keyPair.getCert();
|
||||
if (x509 != null) {
|
||||
JSONObject cert = new JSONObject();
|
||||
cert.put("alias", keyPair.getAlias());
|
||||
try {
|
||||
ASN1Primitive ext = X509ExtensionUtil.fromExtensionValue(x509.getExtensionValue(Extension.basicConstraints.getId()));
|
||||
cert.put("rootca", BasicConstraints.getInstance(ext).isCA());
|
||||
}
|
||||
catch(IOException | NullPointerException e) {
|
||||
cert.put("rootca", false);
|
||||
}
|
||||
cert.put("subject", x509.getSubjectX500Principal().getName());
|
||||
cert.put("expires", SystemUtilities.toISO(x509.getNotAfter()));
|
||||
cert.put("data", formatCert(x509.getEncoded()));
|
||||
certs.put(cert);
|
||||
}
|
||||
}
|
||||
ssl.put("certificates", certs);
|
||||
|
||||
return ssl;
|
||||
}
|
||||
|
||||
public static String formatCert(byte[] encoding) {
|
||||
return "-----BEGIN CERTIFICATE-----\r\n" +
|
||||
new String(Base64.encodeBase64(encoding, true), StandardCharsets.UTF_8) +
|
||||
"-----END CERTIFICATE-----\r\n";
|
||||
}
|
||||
|
||||
private static JSONObject libraries() throws JSONException {
|
||||
JSONObject libraries = new JSONObject();
|
||||
|
||||
SortedMap<String,String> libs = SecurityInfo.getLibVersions();
|
||||
for(Map.Entry<String,String> entry : libs.entrySet()) {
|
||||
String version = entry.getValue();
|
||||
if (version == null) { version = "unknown"; }
|
||||
|
||||
libraries.put(entry.getKey(), version);
|
||||
}
|
||||
|
||||
return libraries;
|
||||
}
|
||||
|
||||
private static JSONObject charsets() throws JSONException {
|
||||
JSONObject charsets = new JSONObject();
|
||||
|
||||
SortedMap<String,Charset> avail = Charset.availableCharsets();
|
||||
ArrayList<String> names = new ArrayList<>();
|
||||
for(Map.Entry<String,Charset> entry : avail.entrySet()) {
|
||||
names.add(entry.getValue().name());
|
||||
}
|
||||
|
||||
charsets.put("charsets", Arrays.toString(names.toArray()));
|
||||
return charsets;
|
||||
}
|
||||
|
||||
public static String getPreferredHostname() {
|
||||
return preferredHostname;
|
||||
}
|
||||
|
||||
public static Version findLatestVersion() {
|
||||
log.trace("Looking for newer versions of {} online", Constants.ABOUT_TITLE);
|
||||
try {
|
||||
URL api = new URL(Constants.VERSION_CHECK_URL);
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(api.openStream()));
|
||||
|
||||
StringBuilder rawJson = new StringBuilder();
|
||||
String line;
|
||||
while((line = br.readLine()) != null) {
|
||||
rawJson.append(line);
|
||||
}
|
||||
|
||||
JSONArray versions = new JSONArray(rawJson.toString());
|
||||
for(int i = 0; i < versions.length(); i++) {
|
||||
JSONObject versionData = versions.getJSONObject(i);
|
||||
if(versionData.getString("target_commitish").equals("master")) {
|
||||
Version latestVersion = Version.valueOf(versionData.getString("name"));
|
||||
log.trace("Found latest version of {} online: {}", Constants.ABOUT_TITLE, latestVersion);
|
||||
return latestVersion;
|
||||
}
|
||||
}
|
||||
throw new Exception("Could not find valid json version information online.");
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.error("Failed to get latest version of {} online", Constants.ABOUT_TITLE, e);
|
||||
}
|
||||
|
||||
return Constants.VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
156
old code/tray/src/qz/common/ByteArrayBuilder.java
Executable file
156
old code/tray/src/qz/common/ByteArrayBuilder.java
Executable file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @author Antoni Ten Monro's
|
||||
*
|
||||
* Copyright (C) 2016 Tres Finocchiaro, QZ Industries, LLC
|
||||
* Copyright (C) 2013 Antoni Ten Monro's
|
||||
*
|
||||
* LGPL 2.1 This is free software. This software and source code are released under
|
||||
* the "LGPL 2.1 License". A copy of this license should be distributed with
|
||||
* this software. http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*
|
||||
*/
|
||||
|
||||
package qz.common;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides a simple and efficient way for concatenating byte arrays, similar
|
||||
* in purpose to <code>StringBuilder</code>. Objects of this class are not
|
||||
* thread safe and include no synchronization
|
||||
*
|
||||
* @author Antoni Ten Monro's
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration") //Library class
|
||||
public final class ByteArrayBuilder {
|
||||
|
||||
private List<Byte> buffer;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new <code>ByteArrayBuilder</code> and sets initial capacity to 10
|
||||
*/
|
||||
public ByteArrayBuilder() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>ByteArrayBuilder</code> and sets initial capacity to
|
||||
* <code>initialCapacity</code>
|
||||
*
|
||||
* @param initialCapacity the initial capacity of the <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public ByteArrayBuilder(int initialCapacity) {
|
||||
this(null, initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>ByteArrayBuilder</code>, sets initial capacity to 10
|
||||
* and appends <code>initialContents</code>
|
||||
*
|
||||
* @param initialContents the initial contents of the ByteArrayBuilder
|
||||
*/
|
||||
public ByteArrayBuilder(byte[] initialContents) {
|
||||
this(initialContents, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>ByteArrayBuilder</code>, sets initial capacity to
|
||||
* <code>initialContents</code> and appends <code>initialContents</code>
|
||||
*
|
||||
* @param initialContents the initial contents of the <code>ByteArrayBuilder</code>
|
||||
* @param initialCapacity the initial capacity of the <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public ByteArrayBuilder(byte[] initialContents, int initialCapacity) {
|
||||
buffer = new ArrayList<>(initialCapacity);
|
||||
if (initialContents != null) {
|
||||
append(initialContents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public void clear() {
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a portion of the <code>ByteArrayBuilder</code>
|
||||
*
|
||||
* @param startIndex Starting index, inclusive
|
||||
* @param endIndex Ending index, exclusive
|
||||
*/
|
||||
public final void clearRange(int startIndex, int endIndex) {
|
||||
buffer.subList(startIndex, endIndex).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the number of bytes currently stored in this <code>ByteArrayBuilder</code>
|
||||
*
|
||||
* @return the number of bytes in the <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public int getLength() {
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a new byte array to this <code>ByteArrayBuilder</code>.
|
||||
* Returns this same object to allow chaining calls
|
||||
*
|
||||
* @param bytes the byte array to append
|
||||
* @return this <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public final ByteArrayBuilder append(byte[] bytes) {
|
||||
for(byte b : bytes) {
|
||||
buffer.add(b);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public final ByteArrayBuilder append(List<Byte> bytes) {
|
||||
for(byte b : bytes) {
|
||||
buffer.add(b);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for append(byte[]) combined with a StringBuffer of specified
|
||||
* charset
|
||||
*
|
||||
* @param string the String to append
|
||||
* @param charset the Charset of the String
|
||||
* @return this <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public final ByteArrayBuilder append(String string, Charset charset) throws UnsupportedEncodingException {
|
||||
return append(string.getBytes(charset.name()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for append(byte[]) combined with a String of specified
|
||||
* charset
|
||||
*
|
||||
* @param stringBuilder the StringBuilder to append
|
||||
* @param charset the Charset of the StringBuilder
|
||||
* @return this <code>ByteArrayBuilder</code>
|
||||
*/
|
||||
public final ByteArrayBuilder append(StringBuilder stringBuilder, Charset charset) throws UnsupportedEncodingException {
|
||||
return append(stringBuilder.toString(), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full contents of this <code>ByteArrayBuilder</code> as
|
||||
* a single <code>byte</code> array.
|
||||
*
|
||||
* @return The contents of this <code>ByteArrayBuilder</code> as a single <code>byte</code> array
|
||||
*/
|
||||
public byte[] getByteArray() {
|
||||
return ArrayUtils.toPrimitive(buffer.toArray(new Byte[buffer.size()]));
|
||||
}
|
||||
}
|
||||
97
old code/tray/src/qz/common/CachedObject.java
Executable file
97
old code/tray/src/qz/common/CachedObject.java
Executable file
@@ -0,0 +1,97 @@
|
||||
package qz.common;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A generic class that encapsulates an object for caching. The cached object
|
||||
* will be refreshed automatically when accessed after its lifespan has expired.
|
||||
*
|
||||
* @param <T> The type of object to be cached.
|
||||
*/
|
||||
public class CachedObject<T> {
|
||||
public static final long DEFAULT_LIFESPAN = 5000; // in milliseconds
|
||||
T lastObject;
|
||||
Supplier<T> supplier;
|
||||
private long timestamp;
|
||||
private long lifespan;
|
||||
|
||||
/**
|
||||
* Creates a new CachedObject with a default lifespan of 5000 milliseconds
|
||||
*
|
||||
* @param supplier The function to pull new values from
|
||||
*/
|
||||
public CachedObject(Supplier<T> supplier) {
|
||||
this(supplier, DEFAULT_LIFESPAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CachedObject
|
||||
*
|
||||
* @param supplier The function to pull new values from
|
||||
* @param lifespan The lifespan of the cached object in milliseconds
|
||||
*/
|
||||
public CachedObject(Supplier<T> supplier, long lifespan) {
|
||||
this.supplier = supplier;
|
||||
setLifespan(lifespan);
|
||||
timestamp = Long.MIN_VALUE; // System.nanoTime() can be negative, MIN_VALUE guarantees a first-run.
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new supplier for the CachedObject
|
||||
*
|
||||
* @param supplier The function to pull new values from
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void registerSupplier(Supplier<T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lifespan of the cached object
|
||||
*
|
||||
* @param milliseconds The lifespan of the cached object in milliseconds
|
||||
*/
|
||||
public void setLifespan(long milliseconds) {
|
||||
lifespan = Math.max(0, milliseconds); // prevent overflow
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cached object.
|
||||
* If the cached object's lifespan has expired, it gets refreshed before being returned.
|
||||
*
|
||||
* @return The cached object
|
||||
*/
|
||||
public T get() {
|
||||
return get(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cached object.
|
||||
* If the cached object's lifespan is expired or forceRefresh is true, it gets refreshed before being returned.
|
||||
*
|
||||
* @param forceRefresh If true, the cached object will be refreshed before being returned regardless of its lifespan
|
||||
* @return The cached object
|
||||
*/
|
||||
public T get(boolean forceRefresh) {
|
||||
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
|
||||
// check lifespan
|
||||
if (forceRefresh || (timestamp + lifespan <= now)) {
|
||||
timestamp = now;
|
||||
lastObject = supplier.get();
|
||||
}
|
||||
return lastObject;
|
||||
}
|
||||
|
||||
// Test
|
||||
public static void main(String ... args) throws InterruptedException {
|
||||
final AtomicInteger testInt = new AtomicInteger(0);
|
||||
|
||||
CachedObject<Integer> cachedString = new CachedObject<>(testInt::incrementAndGet);
|
||||
for(int i = 0; i < 100; i++) {
|
||||
Thread.sleep(1500);
|
||||
System.out.println(cachedString.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
103
old code/tray/src/qz/common/Constants.java
Executable file
103
old code/tray/src/qz/common/Constants.java
Executable file
@@ -0,0 +1,103 @@
|
||||
package qz.common;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import qz.utils.SystemUtilities;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import static qz.ws.SingleInstanceChecker.STEAL_WEBSOCKET_PROPERTY;
|
||||
|
||||
/**
|
||||
* Created by robert on 7/9/2014.
|
||||
*/
|
||||
public class Constants {
|
||||
public static final String HEXES = "0123456789ABCDEF";
|
||||
public static final char[] HEXES_ARRAY = HEXES.toCharArray();
|
||||
public static final int BYTE_BUFFER_SIZE = 8192;
|
||||
public static final Version VERSION = Version.valueOf("2.2.6-SNAPSHOT");
|
||||
public static final Version JAVA_VERSION = SystemUtilities.getJavaVersion();
|
||||
public static final String JAVA_VENDOR = System.getProperty("java.vendor");
|
||||
|
||||
/* QZ-Tray Constants */
|
||||
public static final String BLOCK_FILE = "blocked";
|
||||
public static final String ALLOW_FILE = "allowed";
|
||||
public static final String TEMP_FILE = "temp";
|
||||
public static final String LOG_FILE = "debug";
|
||||
public static final String PROPS_FILE = "qz-tray"; // .properties extension is assumed
|
||||
public static final String PREFS_FILE = "prefs"; // .properties extension is assumed
|
||||
public static final String[] PERSIST_PROPS = {"file.whitelist", "file.allow", "networking.hostname", "networking.port", STEAL_WEBSOCKET_PROPERTY };
|
||||
public static final String AUTOSTART_FILE = ".autostart";
|
||||
public static final String DATA_DIR = "qz";
|
||||
|
||||
public static final int BORDER_PADDING = 10;
|
||||
|
||||
public static final String ABOUT_TITLE = "QZ Tray";
|
||||
public static final String ABOUT_EMAIL = "support@qz.io";
|
||||
public static final String ABOUT_URL = "https://qz.io";
|
||||
public static final String ABOUT_COMPANY = "QZ Industries, LLC";
|
||||
public static final String ABOUT_CITY = "Canastota";
|
||||
public static final String ABOUT_STATE = "NY";
|
||||
public static final String ABOUT_COUNTRY = "US";
|
||||
|
||||
public static final String ABOUT_LICENSING_URL = Constants.ABOUT_URL + "/licensing";
|
||||
public static final String ABOUT_SUPPORT_URL = Constants.ABOUT_URL + "/support";
|
||||
public static final String ABOUT_PRIVACY_URL = Constants.ABOUT_URL + "/privacy";
|
||||
public static final String ABOUT_DOWNLOAD_URL = Constants.ABOUT_URL + "/download";
|
||||
|
||||
public static final String VERSION_CHECK_URL = "https://api.github.com/repos/qzind/tray/releases";
|
||||
public static final String VERSION_DOWNLOAD_URL = "https://github.com/qzind/tray/releases";
|
||||
public static final boolean ENABLE_DIAGNOSTICS = true; // Diagnostics menu (logs, etc)
|
||||
|
||||
public static final String TRUSTED_CERT = String.format("Verified by %s", Constants.ABOUT_COMPANY);
|
||||
public static final String SPONSORED_CERT = String.format("Sponsored by %s", Constants.ABOUT_COMPANY);
|
||||
public static final String SPONSORED_TOOLTIP = "Sponsored organization";
|
||||
public static final String UNTRUSTED_CERT = "Untrusted website";
|
||||
public static final String NO_TRUST = "Cannot verify trust";
|
||||
|
||||
public static final String PROBE_REQUEST = "getProgramName";
|
||||
public static final String PROBE_RESPONSE = ABOUT_TITLE;
|
||||
|
||||
public static final String ALLOW_SITES_TEXT = "Permanently allowed \"%s\" to access local resources";
|
||||
public static final String BLOCK_SITES_TEXT = "Permanently blocked \"%s\" from accessing local resources";
|
||||
|
||||
public static final String REMEMBER_THIS_DECISION = "Remember this decision";
|
||||
public static final String STRICT_MODE_LABEL = "Use strict certificate mode";
|
||||
public static final String STRICT_MODE_TOOLTIP = String.format("Prevents the ability to select \"%s\" for most websites", REMEMBER_THIS_DECISION);
|
||||
public static final String STRICT_MODE_CONFIRM = String.format("Set strict certificate mode? Most websites will stop working with %s.", ABOUT_TITLE);
|
||||
public static final String ALLOW_SITES_LABEL = "Sites permanently allowed access";
|
||||
public static final String BLOCK_SITES_LABEL = "Sites permanently blocked from access";
|
||||
|
||||
|
||||
public static final String ALLOWED = "Allowed";
|
||||
public static final String BLOCKED = "Blocked";
|
||||
|
||||
public static final String OVERRIDE_CERT = "override.crt";
|
||||
public static final String WHITELIST_CERT_DIR = "whitelist";
|
||||
public static final String PROVISION_DIR = "provision";
|
||||
public static final String PROVISION_FILE = "provision.json";
|
||||
|
||||
public static final String SIGNING_PRIVATE_KEY = "private-key.pem";
|
||||
public static final String SIGNING_CERTIFICATE = "digital-certificate.txt";
|
||||
|
||||
public static final long VALID_SIGNING_PERIOD = 15 * 60 * 1000; //millis
|
||||
public static final int EXPIRY_WARN = 30; // days
|
||||
public static final Color WARNING_COLOR_LITE = Color.RED;
|
||||
public static final Color TRUSTED_COLOR_LITE = Color.BLUE;
|
||||
public static final Color WARNING_COLOR_DARK = Color.decode("#EB6261");
|
||||
public static final Color TRUSTED_COLOR_DARK = Color.decode("#589DF6");
|
||||
public static Color WARNING_COLOR = WARNING_COLOR_LITE;
|
||||
public static Color TRUSTED_COLOR = TRUSTED_COLOR_LITE;
|
||||
|
||||
public static boolean MASK_TRAY_SUPPORTED = true;
|
||||
|
||||
public static final long MEMORY_PER_PRINT = 512; //MB
|
||||
|
||||
public static final String RAW_PRINT = ABOUT_TITLE + " Raw Print";
|
||||
public static final String IMAGE_PRINT = ABOUT_TITLE + " Pixel Print";
|
||||
public static final String PDF_PRINT = ABOUT_TITLE + " PDF Print";
|
||||
public static final String HTML_PRINT = ABOUT_TITLE + " HTML Print";
|
||||
|
||||
public static final Integer[] DEFAULT_WSS_PORTS = {8181, 8282, 8383, 8484};
|
||||
public static final Integer[] DEFAULT_WS_PORTS = {8182, 8283, 8384, 8485};
|
||||
public static final Integer[] CUPS_RSS_PORTS = {8586, 8687, 8788, 8889};
|
||||
}
|
||||
100
old code/tray/src/qz/common/PropertyHelper.java
Executable file
100
old code/tray/src/qz/common/PropertyHelper.java
Executable file
@@ -0,0 +1,100 @@
|
||||
package qz.common;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.utils.ArgValue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
/**
|
||||
* Created by Tres on 12/16/2015.
|
||||
*/
|
||||
public class PropertyHelper extends Properties {
|
||||
private static final Logger log = LogManager.getLogger(PropertyHelper.class);
|
||||
private String file;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public PropertyHelper() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
* @param p Initial Properties
|
||||
*/
|
||||
public PropertyHelper(Properties p) {
|
||||
super(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom constructor, attempts to load from file
|
||||
* @param file File to load properties from
|
||||
*/
|
||||
public PropertyHelper(String file) {
|
||||
super();
|
||||
this.file = file;
|
||||
load(file);
|
||||
}
|
||||
|
||||
public PropertyHelper(File file) {
|
||||
this(file == null ? null : file.getAbsolutePath());
|
||||
}
|
||||
|
||||
public boolean getBoolean(String key, boolean defaultVal) {
|
||||
String prop = getProperty(key);
|
||||
if (prop != null) {
|
||||
return Boolean.parseBoolean(prop);
|
||||
} else {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
public void setProperty(ArgValue arg, boolean value) {
|
||||
setProperty(arg.getMatch(), "" + value);
|
||||
}
|
||||
|
||||
public void load(File file) {
|
||||
load(file == null ? null : file.getAbsolutePath());
|
||||
}
|
||||
|
||||
public void load(String file) {
|
||||
FileInputStream f = null;
|
||||
try {
|
||||
f = new FileInputStream(file);
|
||||
load(f);
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not load file: {}, reason: {}", file, e.getLocalizedMessage());
|
||||
} finally {
|
||||
if (f != null) {
|
||||
try { f.close(); } catch(Throwable ignore) {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean save() {
|
||||
boolean success = false;
|
||||
FileOutputStream f = null;
|
||||
try {
|
||||
f = new FileOutputStream(file);
|
||||
this.store(f, null);
|
||||
success = true;
|
||||
} catch (IOException e) {
|
||||
log.error("Error saving file: {}", file, e);
|
||||
} finally {
|
||||
if (f != null) {
|
||||
try { f.close(); } catch(Throwable ignore) {};
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public synchronized Object setProperty(Map.Entry<String, String> pair) {
|
||||
return super.setProperty(pair.getKey(), pair.getValue());
|
||||
}
|
||||
}
|
||||
169
old code/tray/src/qz/common/SecurityInfo.java
Executable file
169
old code/tray/src/qz/common/SecurityInfo.java
Executable file
@@ -0,0 +1,169 @@
|
||||
package qz.common;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.eclipse.jetty.util.Jetty;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.usb4java.LibUsb;
|
||||
import purejavahidapi.PureJavaHidApi;
|
||||
import qz.utils.SystemUtilities;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by Kyle B. on 10/27/2017.
|
||||
*/
|
||||
public class SecurityInfo {
|
||||
/**
|
||||
* Wrap throwable operations into a try/catch
|
||||
*/
|
||||
private static class CheckedTreeMap<K, V> extends TreeMap<K, V> {
|
||||
private static final Logger log = LogManager.getLogger(CheckedTreeMap.class);
|
||||
|
||||
interface CheckedValue {
|
||||
Object check() throws Throwable;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public V put(K key, CheckedValue value) {
|
||||
try {
|
||||
return put(key, (V)value.check());
|
||||
} catch(Throwable t) {
|
||||
log.warn("A checked exception was suppressed adding key \"{}\"", key, t);
|
||||
return put(key, (V)"missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger log = LogManager.getLogger(SecurityInfo.class);
|
||||
|
||||
public static KeyStore getKeyStore(Properties props) {
|
||||
if (props != null) {
|
||||
String store = props.getProperty("wss.keystore", "");
|
||||
char[] pass = props.getProperty("wss.storepass", "").toCharArray();
|
||||
|
||||
try {
|
||||
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keystore.load(new FileInputStream(store), pass);
|
||||
return keystore;
|
||||
}
|
||||
catch(GeneralSecurityException | IOException e) {
|
||||
log.warn("Unable to create keystore from properties file: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SortedMap<String,String> getLibVersions() {
|
||||
CheckedTreeMap<String,String> libVersions = new CheckedTreeMap<>();
|
||||
|
||||
// Use API-provided mechanism if available
|
||||
libVersions.put("jna (native)", () -> Native.VERSION_NATIVE);
|
||||
libVersions.put("jna (location)", () -> {
|
||||
@SuppressWarnings("unused")
|
||||
int ignore = Native.BOOL_SIZE;
|
||||
return System.getProperty("jnidispatch.path");
|
||||
});
|
||||
libVersions.put("jna", Native.VERSION);
|
||||
libVersions.put("jssc", () -> jssc.SerialNativeInterface.getLibraryVersion());
|
||||
libVersions.put("jssc (native)", () -> jssc.SerialNativeInterface.getNativeLibraryVersion());
|
||||
libVersions.put("jetty", Jetty.VERSION);
|
||||
libVersions.put("pdfbox", org.apache.pdfbox.util.Version.getVersion());
|
||||
libVersions.put("purejavahidapi", () -> PureJavaHidApi.getVersion());
|
||||
libVersions.put("usb-api", javax.usb.Version.getApiVersion());
|
||||
libVersions.put("not-yet-commons-ssl", org.apache.commons.ssl.Version.VERSION);
|
||||
libVersions.put("mslinks", mslinks.ShellLink.VERSION);
|
||||
libVersions.put("bouncycastle", "" + new BouncyCastleProvider().getVersion());
|
||||
libVersions.put("usb4java (native)", () -> LibUsb.getVersion().toString());
|
||||
|
||||
libVersions.put("jre", Constants.JAVA_VERSION.toString());
|
||||
libVersions.put("jre (vendor)", Constants.JAVA_VENDOR);
|
||||
|
||||
//JFX info, if it exists
|
||||
try {
|
||||
// "DO NOT LINK JAVAFX EVER" - JavaFX may not exist, use reflection to avoid compilation errors
|
||||
Class<?> VersionInfo = Class.forName("com.sun.javafx.runtime.VersionInfo");
|
||||
Path fxPath = Paths.get(VersionInfo.getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
Method method = VersionInfo.getMethod("getVersion");
|
||||
Object version = method.invoke(null);
|
||||
libVersions.put("javafx", (String)version);
|
||||
libVersions.put("javafx (location)", fxPath.toString());
|
||||
} catch(Throwable e) {
|
||||
libVersions.put("javafx", "missing");
|
||||
libVersions.put("javafx (location)", "missing");
|
||||
}
|
||||
|
||||
// Fallback to maven manifest information
|
||||
HashMap<String,String> mavenVersions = getMavenVersions();
|
||||
|
||||
String[] mavenLibs = {"jetty-servlet", "jetty-io", "websocket-common",
|
||||
"usb4java-javax", "java-semver", "commons-pool2",
|
||||
"websocket-server", "jettison", "commons-codec", "log4j-api", "log4j-core",
|
||||
"websocket-servlet", "jetty-http", "commons-lang3", "javax-websocket-server-impl",
|
||||
"javax.servlet-api", "hid4java", "usb4java", "websocket-api", "jetty-util", "websocket-client",
|
||||
"javax.websocket-api", "commons-io", "jetty-security"};
|
||||
|
||||
for(String lib : mavenLibs) {
|
||||
libVersions.put(lib, mavenVersions.get(lib));
|
||||
}
|
||||
|
||||
return libVersions;
|
||||
}
|
||||
|
||||
public static void printLibInfo() {
|
||||
String format = "%-40s%s%n";
|
||||
System.out.printf(format, "LIBRARY NAME:", "VERSION:");
|
||||
SortedMap<String,String> libVersions = SecurityInfo.getLibVersions();
|
||||
for(Map.Entry<String,String> entry : libVersions.entrySet()) {
|
||||
if (entry.getValue() == null) {
|
||||
System.out.printf(format, entry.getKey(), "(unknown)");
|
||||
} else {
|
||||
System.out.printf(format, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches embedded version information based on maven properties
|
||||
*
|
||||
* @return HashMap of library name, version
|
||||
*/
|
||||
private static HashMap<String,String> getMavenVersions() {
|
||||
final HashMap<String,String> mavenVersions = new HashMap<>();
|
||||
String jar = "jar:" + SecurityInfo.class.getProtectionDomain().getCodeSource().getLocation().toString();
|
||||
try(FileSystem fs = FileSystems.newFileSystem(new URI(jar), new HashMap<String,String>())) {
|
||||
Files.walkFileTree(fs.getPath("/META-INF/maven"), new HashSet<>(), 3, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (file.toString().endsWith(".properties")) {
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.load(Files.newInputStream(file, StandardOpenOption.READ));
|
||||
mavenVersions.put(props.getProperty("artifactId"), props.getProperty("version"));
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.warn("Error reading properties from {}", file, e);
|
||||
}
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(Exception ignore) {
|
||||
log.warn("Could not open {} for version information. Most libraries will list as (unknown)", jar);
|
||||
}
|
||||
return mavenVersions;
|
||||
}
|
||||
|
||||
}
|
||||
693
old code/tray/src/qz/common/TrayManager.java
Executable file
693
old code/tray/src/qz/common/TrayManager.java
Executable file
@@ -0,0 +1,693 @@
|
||||
/**
|
||||
* @author Tres Finocchiaro
|
||||
*
|
||||
* Copyright (C) 2016 Tres Finocchiaro, QZ Industries, LLC
|
||||
*
|
||||
* LGPL 2.1 This is free software. This software and source code are released under
|
||||
* the "LGPL 2.1 License". A copy of this license should be distributed with
|
||||
* this software. http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
|
||||
package qz.common;
|
||||
|
||||
import com.github.zafarkhaja.semver.Version;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import qz.App;
|
||||
import qz.auth.Certificate;
|
||||
import qz.auth.RequestState;
|
||||
import qz.installer.shortcut.ShortcutCreator;
|
||||
import qz.printer.PrintServiceMatcher;
|
||||
import qz.printer.action.html.WebApp;
|
||||
import qz.ui.*;
|
||||
import qz.ui.component.IconCache;
|
||||
import qz.ui.tray.TrayType;
|
||||
import qz.utils.*;
|
||||
import qz.ws.PrintSocketServer;
|
||||
import qz.ws.SingleInstanceChecker;
|
||||
import qz.ws.WebsocketPorts;
|
||||
import qz.ws.substitutions.Substitutions;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static qz.ui.component.IconCache.Icon.*;
|
||||
import static qz.utils.ArgValue.*;
|
||||
|
||||
/**
|
||||
* Manages the icons and actions associated with the TrayIcon
|
||||
*
|
||||
* @author Tres Finocchiaro
|
||||
*/
|
||||
public class TrayManager {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(TrayManager.class);
|
||||
|
||||
private boolean headless;
|
||||
|
||||
// The cached icons
|
||||
private final IconCache iconCache;
|
||||
|
||||
// Custom swing pop-up menu
|
||||
private TrayType tray;
|
||||
|
||||
private ConfirmDialog confirmDialog;
|
||||
private GatewayDialog gatewayDialog;
|
||||
private AboutDialog aboutDialog;
|
||||
private LogDialog logDialog;
|
||||
private SiteManagerDialog sitesDialog;
|
||||
private ArrayList<Component> componentList;
|
||||
private IconCache.Icon shownIcon;
|
||||
|
||||
// Need a class reference to this so we can set it from the request dialog window
|
||||
private JCheckBoxMenuItem anonymousItem;
|
||||
|
||||
// The name this UI component will use, i.e "QZ Print 1.9.0"
|
||||
private final String name;
|
||||
|
||||
// The shortcut and startup helper
|
||||
private final ShortcutCreator shortcutCreator;
|
||||
|
||||
private final PropertyHelper prefs;
|
||||
|
||||
// Action to run when reload is triggered
|
||||
private Thread reloadThread;
|
||||
|
||||
// Actions to run if idle after startup
|
||||
private java.util.Timer idleTimer = new java.util.Timer();
|
||||
|
||||
public TrayManager() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a AutoHideJSystemTray with the specified name/text
|
||||
*/
|
||||
public TrayManager(boolean isHeadless) {
|
||||
name = Constants.ABOUT_TITLE + " " + Constants.VERSION;
|
||||
|
||||
prefs = new PropertyHelper(FileUtilities.USER_DIR + File.separator + Constants.PREFS_FILE + ".properties");
|
||||
prefs.remove(SECURITY_FILE_STRICT.getMatch()); // per https://github.com/qzind/tray/issues/1337
|
||||
|
||||
// Set strict certificate mode preference
|
||||
Certificate.setTrustBuiltIn(!getPref(TRAY_STRICTMODE));
|
||||
|
||||
// Configures JSON websocket messages
|
||||
Substitutions.getInstance();
|
||||
|
||||
// Set FileIO security
|
||||
FileUtilities.setFileIoEnabled(getPref(SECURITY_FILE_ENABLED));
|
||||
FileUtilities.setFileIoStrict(getPref(SECURITY_FILE_STRICT));
|
||||
|
||||
// Headless if turned on by user or unsupported by environment
|
||||
headless = isHeadless || getPref(HEADLESS) || GraphicsEnvironment.isHeadless();
|
||||
if (headless) {
|
||||
log.info("Running in headless mode");
|
||||
}
|
||||
|
||||
// Set up the shortcut name so that the UI components can use it
|
||||
shortcutCreator = ShortcutCreator.getInstance();
|
||||
|
||||
SystemUtilities.setSystemLookAndFeel(headless);
|
||||
iconCache = new IconCache();
|
||||
|
||||
if (SystemUtilities.isSystemTraySupported(headless)) { // UI mode with tray
|
||||
switch(SystemUtilities.getOs()) {
|
||||
case WINDOWS:
|
||||
tray = TrayType.JX.init(iconCache);
|
||||
// Undocumented HiDPI behavior
|
||||
tray.setImageAutoSize(true);
|
||||
break;
|
||||
case MAC:
|
||||
tray = TrayType.CLASSIC.init(iconCache);
|
||||
break;
|
||||
default:
|
||||
tray = TrayType.MODERN.init(iconCache);
|
||||
}
|
||||
|
||||
// OS-specific tray icon handling
|
||||
if (SystemTray.isSupported()) {
|
||||
iconCache.fixTrayIcons(SystemUtilities.isDarkTaskbar());
|
||||
}
|
||||
|
||||
// Iterates over all images denoted by IconCache.getTypes() and caches them
|
||||
tray.setIcon(DANGER_ICON);
|
||||
tray.setToolTip(name);
|
||||
|
||||
try {
|
||||
SystemTray.getSystemTray().add(tray.tray());
|
||||
}
|
||||
catch(AWTException awt) {
|
||||
log.error("Could not attach tray, forcing headless mode", awt);
|
||||
headless = true;
|
||||
}
|
||||
} else if (!headless) { // UI mode without tray
|
||||
tray = TrayType.TASKBAR.init(exitListener, iconCache);
|
||||
tray.setIcon(DANGER_ICON);
|
||||
tray.setToolTip(name);
|
||||
tray.showTaskbar();
|
||||
}
|
||||
|
||||
// TODO: Remove when fixed upstream. See issue #393
|
||||
if (SystemUtilities.isUnix() && !isHeadless) {
|
||||
// Update printer list in CUPS immediately (normally 2min)
|
||||
System.setProperty("sun.java2d.print.polling", "false");
|
||||
}
|
||||
|
||||
if (!headless) {
|
||||
componentList = new ArrayList<>();
|
||||
|
||||
// The allow/block dialog
|
||||
gatewayDialog = new GatewayDialog(null, "Action Required", iconCache);
|
||||
componentList.add(gatewayDialog);
|
||||
|
||||
// The ok/cancel dialog
|
||||
confirmDialog = new ConfirmDialog(null, "Please Confirm", iconCache);
|
||||
componentList.add(confirmDialog);
|
||||
|
||||
// Detect theme changes
|
||||
new Thread(() -> {
|
||||
boolean darkDesktopMode = SystemUtilities.isDarkDesktop();
|
||||
boolean darkTaskbarMode = SystemUtilities.isDarkTaskbar();
|
||||
while(true) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
if (darkDesktopMode != SystemUtilities.isDarkDesktop(true) ||
|
||||
darkTaskbarMode != SystemUtilities.isDarkTaskbar(true)) {
|
||||
darkDesktopMode = SystemUtilities.isDarkDesktop();
|
||||
darkTaskbarMode = SystemUtilities.isDarkTaskbar();
|
||||
iconCache.fixTrayIcons(darkTaskbarMode);
|
||||
refreshIcon(null);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
SystemUtilities.setSystemLookAndFeel(headless);
|
||||
for(Component c : componentList) {
|
||||
SwingUtilities.updateComponentTreeUI(c);
|
||||
if (c instanceof Themeable) {
|
||||
((Themeable)c).refresh();
|
||||
}
|
||||
if (c instanceof JDialog) {
|
||||
((JDialog)c).pack();
|
||||
} else if (c instanceof JPopupMenu) {
|
||||
((JPopupMenu)c).pack();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(InterruptedException ignore) {}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
if (tray != null) {
|
||||
addMenuItems();
|
||||
}
|
||||
|
||||
// Initialize idle actions
|
||||
// Slow to start JavaFX the first time
|
||||
if (getPref(TRAY_IDLE_JAVAFX)) {
|
||||
performIfIdle((int)TimeUnit.SECONDS.toMillis(60), evt -> {
|
||||
log.debug("IDLE: Starting up JFX for HTML printing");
|
||||
try {
|
||||
WebApp.initialize();
|
||||
}
|
||||
catch(IOException e) {
|
||||
log.error("Idle runner failed to preemptively start JavaFX service");
|
||||
}
|
||||
});
|
||||
}
|
||||
// Slow to find printers the first time if a lot of printers are installed
|
||||
// Must run after JavaFX per https://github.com/qzind/tray/issues/924
|
||||
if (getPref(TRAY_IDLE_PRINTERS)) {
|
||||
performIfIdle((int)TimeUnit.SECONDS.toMillis(120), evt -> {
|
||||
log.debug("IDLE: Performing first run of find printers");
|
||||
PrintServiceMatcher.getNativePrinterList(false, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stand-alone invocation of TrayManager
|
||||
*
|
||||
* @param args arguments to pass to main
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
SwingUtilities.invokeLater(TrayManager::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the swing pop-up menu with the specified items
|
||||
*/
|
||||
private void addMenuItems() {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
componentList.add(popup);
|
||||
|
||||
JMenu advancedMenu = new JMenu("Advanced");
|
||||
advancedMenu.setMnemonic(KeyEvent.VK_A);
|
||||
advancedMenu.setIcon(iconCache.getIcon(SETTINGS_ICON));
|
||||
|
||||
JMenuItem sitesItem = new JMenuItem("Site Manager...", iconCache.getIcon(SAVED_ICON));
|
||||
sitesItem.setMnemonic(KeyEvent.VK_M);
|
||||
sitesItem.addActionListener(savedListener);
|
||||
sitesDialog = new SiteManagerDialog(sitesItem, iconCache, prefs);
|
||||
componentList.add(sitesDialog);
|
||||
|
||||
JMenuItem pairingConfigItem = new JMenuItem("Pairing Configuration...", iconCache.getIcon(SETTINGS_ICON));
|
||||
pairingConfigItem.setMnemonic(KeyEvent.VK_P);
|
||||
pairingConfigItem.addActionListener(e -> new qz.ui.PairingConfigDialog(null).setVisible(true));
|
||||
advancedMenu.add(pairingConfigItem);
|
||||
|
||||
JMenuItem diagnosticMenu = new JMenu("Diagnostic");
|
||||
|
||||
JMenuItem browseApp = new JMenuItem("Browse App folder...", iconCache.getIcon(FOLDER_ICON));
|
||||
browseApp.setToolTipText(SystemUtilities.getJarParentPath().toString());
|
||||
browseApp.setMnemonic(KeyEvent.VK_O);
|
||||
browseApp.addActionListener(e -> ShellUtilities.browseAppDirectory());
|
||||
diagnosticMenu.add(browseApp);
|
||||
|
||||
JMenuItem browseUser = new JMenuItem("Browse User folder...", iconCache.getIcon(FOLDER_ICON));
|
||||
browseUser.setToolTipText(FileUtilities.USER_DIR.toString());
|
||||
browseUser.setMnemonic(KeyEvent.VK_U);
|
||||
browseUser.addActionListener(e -> ShellUtilities.browseDirectory(FileUtilities.USER_DIR));
|
||||
diagnosticMenu.add(browseUser);
|
||||
|
||||
JMenuItem browseShared = new JMenuItem("Browse Shared folder...", iconCache.getIcon(FOLDER_ICON));
|
||||
browseShared.setToolTipText(FileUtilities.SHARED_DIR.toString());
|
||||
browseShared.setMnemonic(KeyEvent.VK_S);
|
||||
browseShared.addActionListener(e -> ShellUtilities.browseDirectory(FileUtilities.SHARED_DIR));
|
||||
diagnosticMenu.add(browseShared);
|
||||
|
||||
diagnosticMenu.add(new JSeparator());
|
||||
|
||||
JCheckBoxMenuItem notificationsItem = new JCheckBoxMenuItem("Show all notifications");
|
||||
notificationsItem.setToolTipText("Shows all connect/disconnect messages, useful for debugging purposes");
|
||||
notificationsItem.setMnemonic(KeyEvent.VK_S);
|
||||
notificationsItem.setState(getPref(TRAY_NOTIFICATIONS));
|
||||
notificationsItem.addActionListener(notificationsListener);
|
||||
diagnosticMenu.add(notificationsItem);
|
||||
|
||||
JCheckBoxMenuItem monocleItem = new JCheckBoxMenuItem("Use Monocle for HTML");
|
||||
monocleItem.setToolTipText("Use monocle platform for HTML printing (restart required)");
|
||||
monocleItem.setMnemonic(KeyEvent.VK_U);
|
||||
monocleItem.setState(getPref(TRAY_MONOCLE));
|
||||
if(!SystemUtilities.hasMonocle()) {
|
||||
log.warn("Monocle engine was not detected");
|
||||
monocleItem.setEnabled(false);
|
||||
monocleItem.setToolTipText("Monocle HTML engine was not detected");
|
||||
}
|
||||
monocleItem.addActionListener(monocleListener);
|
||||
|
||||
if (Constants.JAVA_VERSION.greaterThanOrEqualTo(Version.valueOf("11.0.0"))) { //only include if it can be used
|
||||
diagnosticMenu.add(monocleItem);
|
||||
}
|
||||
|
||||
diagnosticMenu.add(new JSeparator());
|
||||
|
||||
JMenuItem logItem = new JMenuItem("View logs (live feed)...", iconCache.getIcon(LOG_ICON));
|
||||
logItem.setMnemonic(KeyEvent.VK_L);
|
||||
logItem.addActionListener(logListener);
|
||||
diagnosticMenu.add(logItem);
|
||||
logDialog = new LogDialog(logItem, iconCache);
|
||||
componentList.add(logDialog);
|
||||
|
||||
JMenuItem zipLogs = new JMenuItem("Zip logs (to Desktop)");
|
||||
zipLogs.setToolTipText("Zip diagnostic logs, place on Desktop");
|
||||
zipLogs.setMnemonic(KeyEvent.VK_Z);
|
||||
zipLogs.addActionListener(e -> FileUtilities.zipLogs());
|
||||
diagnosticMenu.add(zipLogs);
|
||||
|
||||
JMenuItem desktopItem = new JMenuItem("Create Desktop shortcut", iconCache.getIcon(DESKTOP_ICON));
|
||||
desktopItem.setMnemonic(KeyEvent.VK_D);
|
||||
desktopItem.addActionListener(desktopListener());
|
||||
|
||||
anonymousItem = new JCheckBoxMenuItem("Block anonymous requests");
|
||||
anonymousItem.setToolTipText("Blocks all requests that do not contain a valid certificate/signature");
|
||||
anonymousItem.setMnemonic(KeyEvent.VK_K);
|
||||
anonymousItem.setState(Certificate.UNKNOWN.isBlocked());
|
||||
anonymousItem.addActionListener(anonymousListener);
|
||||
|
||||
if(Constants.ENABLE_DIAGNOSTICS) {
|
||||
advancedMenu.add(diagnosticMenu);
|
||||
advancedMenu.add(new JSeparator());
|
||||
}
|
||||
advancedMenu.add(sitesItem);
|
||||
advancedMenu.add(desktopItem);
|
||||
advancedMenu.add(new JSeparator());
|
||||
advancedMenu.add(anonymousItem);
|
||||
|
||||
JMenuItem reloadItem = new JMenuItem("Reload", iconCache.getIcon(RELOAD_ICON));
|
||||
reloadItem.setMnemonic(KeyEvent.VK_R);
|
||||
reloadItem.addActionListener(reloadListener);
|
||||
|
||||
JMenuItem aboutItem = new JMenuItem("About...", iconCache.getIcon(ABOUT_ICON));
|
||||
aboutItem.setMnemonic(KeyEvent.VK_B);
|
||||
aboutItem.addActionListener(aboutListener);
|
||||
aboutDialog = new AboutDialog(aboutItem, iconCache);
|
||||
componentList.add(aboutDialog);
|
||||
|
||||
if (SystemUtilities.isMac()) {
|
||||
MacUtilities.registerAboutDialog(aboutDialog);
|
||||
MacUtilities.registerQuitHandler(this);
|
||||
}
|
||||
|
||||
JSeparator separator = new JSeparator();
|
||||
|
||||
JCheckBoxMenuItem startupItem = new JCheckBoxMenuItem("Automatically start");
|
||||
startupItem.setMnemonic(KeyEvent.VK_S);
|
||||
startupItem.setState(FileUtilities.isAutostart());
|
||||
startupItem.addActionListener(startupListener());
|
||||
if (!shortcutCreator.canAutoStart()) {
|
||||
startupItem.setEnabled(false);
|
||||
startupItem.setState(false);
|
||||
startupItem.setToolTipText("Autostart has been disabled by the administrator");
|
||||
}
|
||||
|
||||
JMenuItem exitItem = new JMenuItem("Exit", iconCache.getIcon(EXIT_ICON));
|
||||
exitItem.addActionListener(exitListener);
|
||||
|
||||
popup.add(advancedMenu);
|
||||
popup.add(reloadItem);
|
||||
popup.add(aboutItem);
|
||||
popup.add(startupItem);
|
||||
popup.add(separator);
|
||||
popup.add(exitItem);
|
||||
|
||||
if (tray != null) {
|
||||
tray.setJPopupMenu(popup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final ActionListener notificationsListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
prefs.setProperty(TRAY_NOTIFICATIONS, ((JCheckBoxMenuItem)e.getSource()).getState());
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener monocleListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JCheckBoxMenuItem j = (JCheckBoxMenuItem)e.getSource();
|
||||
prefs.setProperty(TRAY_MONOCLE, j.getState());
|
||||
displayWarningMessage(String.format("A restart of %s is required to ensure this feature is %sabled.",
|
||||
Constants.ABOUT_TITLE, j.getState()? "en":"dis"));
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener desktopListener() {
|
||||
return e -> {
|
||||
shortcutCreator.createDesktopShortcut();
|
||||
};
|
||||
}
|
||||
|
||||
private final ActionListener savedListener = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
sitesDialog.setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener anonymousListener = e -> {
|
||||
boolean checkBoxState = true;
|
||||
if (e.getSource() instanceof JCheckBoxMenuItem) {
|
||||
checkBoxState = ((JCheckBoxMenuItem)e.getSource()).getState();
|
||||
}
|
||||
|
||||
log.debug("Block unsigned: {}", checkBoxState);
|
||||
|
||||
if (checkBoxState) {
|
||||
blackList(Certificate.UNKNOWN);
|
||||
} else {
|
||||
FileUtilities.deleteFromFile(Constants.BLOCK_FILE, Certificate.UNKNOWN.data(), true);
|
||||
FileUtilities.deleteFromFile(Constants.BLOCK_FILE, Certificate.UNKNOWN.data(), false);
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener logListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
logDialog.setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
private ActionListener startupListener() {
|
||||
return e -> {
|
||||
JCheckBoxMenuItem source = (JCheckBoxMenuItem)e.getSource();
|
||||
if (!source.getState() && !confirmDialog.prompt("Remove " + name + " from startup?")) {
|
||||
source.setState(true);
|
||||
return;
|
||||
}
|
||||
if (FileUtilities.setAutostart(source.getState())) {
|
||||
displayInfoMessage("Successfully " + (source.getState() ? "enabled" : "disabled") + " autostart");
|
||||
} else {
|
||||
displayErrorMessage("Error " + (source.getState() ? "enabling" : "disabling") + " autostart");
|
||||
}
|
||||
source.setState(FileUtilities.isAutostart());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default reload action (in this case, <code>Thread.start()</code>) to be fired
|
||||
*
|
||||
* @param reloadThread The Thread to call when reload is clicked
|
||||
*/
|
||||
public void setReloadThread(Thread reloadThread) {
|
||||
this.reloadThread = reloadThread;
|
||||
}
|
||||
|
||||
private ActionListener reloadListener = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (reloadThread == null) {
|
||||
showErrorDialog("Sorry, Reload has not yet been implemented.");
|
||||
} else {
|
||||
reloadThread.start();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener aboutListener = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
aboutDialog.setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener exitListener = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
boolean showAllNotifications = getPref(TRAY_NOTIFICATIONS);
|
||||
if (!showAllNotifications || confirmDialog.prompt("Exit " + name + "?")) { exit(0); }
|
||||
}
|
||||
};
|
||||
|
||||
public void exit(int returnCode) {
|
||||
prefs.save();
|
||||
FileUtilities.cleanup();
|
||||
System.exit(returnCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a basic error dialog.
|
||||
*/
|
||||
private void showErrorDialog(String message) {
|
||||
JOptionPane.showMessageDialog(null, message, name, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
public boolean showGatewayDialog(final RequestState request, final String prompt, final Point position) {
|
||||
if (!headless) {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> gatewayDialog.prompt("%s wants to " + prompt, request, position));
|
||||
}
|
||||
catch(Exception ignore) {}
|
||||
|
||||
if (gatewayDialog.isApproved()) {
|
||||
log.info("Allowed {} to {}", request.getCertName(), prompt);
|
||||
if (gatewayDialog.isPersistent()) {
|
||||
whiteList(request.getCertUsed());
|
||||
}
|
||||
} else {
|
||||
log.info("Denied {} to {}", request.getCertName(), prompt);
|
||||
if (gatewayDialog.isPersistent()) {
|
||||
if (!request.hasCertificate()) {
|
||||
anonymousItem.doClick(); // if always block anonymous requests -> flag menu item
|
||||
} else {
|
||||
blackList(request.getCertUsed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gatewayDialog.isApproved();
|
||||
} else {
|
||||
return request.hasSavedCert();
|
||||
}
|
||||
}
|
||||
|
||||
private void whiteList(Certificate cert) {
|
||||
if (FileUtilities.printLineToFile(Constants.ALLOW_FILE, cert.data())) {
|
||||
displayInfoMessage(String.format(Constants.ALLOW_SITES_TEXT, cert.getOrganization()));
|
||||
} else {
|
||||
displayErrorMessage("Failed to write to file (Insufficient user privileges)");
|
||||
}
|
||||
}
|
||||
|
||||
private void blackList(Certificate cert) {
|
||||
if (FileUtilities.printLineToFile(Constants.BLOCK_FILE, cert.data())) {
|
||||
displayInfoMessage(String.format(Constants.BLOCK_SITES_TEXT, cert.getOrganization()));
|
||||
} else {
|
||||
displayErrorMessage("Failed to write to file (Insufficient user privileges)");
|
||||
}
|
||||
}
|
||||
|
||||
public void setServer(Server server, WebsocketPorts websocketPorts) {
|
||||
if (server != null && server.getConnectors().length > 0) {
|
||||
singleInstanceCheck(websocketPorts);
|
||||
|
||||
displayInfoMessage("Server started on port(s) " + PrintSocketServer.getPorts(server));
|
||||
|
||||
if (!headless) {
|
||||
aboutDialog.setServer(server);
|
||||
setDefaultIcon();
|
||||
}
|
||||
} else {
|
||||
displayErrorMessage("Invalid server");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe method for setting a fine status message. Messages are suppressed unless "Show all
|
||||
* notifications" is checked.
|
||||
*/
|
||||
public void displayInfoMessage(String text) {
|
||||
displayMessage(name, text, TrayIcon.MessageType.INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe method for setting the default icon
|
||||
*/
|
||||
public void setDefaultIcon() {
|
||||
// Workaround for JDK-8252015
|
||||
if(SystemUtilities.isMac() && Constants.MASK_TRAY_SUPPORTED && !MacUtilities.jdkSupportsTemplateIcon()) {
|
||||
setIcon(DEFAULT_ICON, () -> MacUtilities.toggleTemplateIcon(tray.tray()));
|
||||
} else {
|
||||
setIcon(DEFAULT_ICON);
|
||||
}
|
||||
}
|
||||
|
||||
/** Thread safe method for setting the error status message */
|
||||
public void displayErrorMessage(String text) {
|
||||
displayMessage(name, text, TrayIcon.MessageType.ERROR);
|
||||
}
|
||||
|
||||
/** Thread safe method for setting the danger icon */
|
||||
public void setDangerIcon() {
|
||||
setIcon(DANGER_ICON);
|
||||
}
|
||||
|
||||
/** Thread safe method for setting the warning status message */
|
||||
public void displayWarningMessage(String text) {
|
||||
displayMessage(name, text, TrayIcon.MessageType.WARNING);
|
||||
}
|
||||
|
||||
/** Thread safe method for setting the warning icon */
|
||||
public void setWarningIcon() {
|
||||
setIcon(WARNING_ICON);
|
||||
}
|
||||
|
||||
/** Thread safe method for setting the specified icon */
|
||||
private void setIcon(final IconCache.Icon i, Runnable whenDone) {
|
||||
if (tray != null && i != shownIcon) {
|
||||
shownIcon = i;
|
||||
refreshIcon(whenDone);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIcon(final IconCache.Icon i) {
|
||||
setIcon(i, null);
|
||||
}
|
||||
|
||||
public void refreshIcon(final Runnable whenDone) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
tray.setIcon(shownIcon);
|
||||
if(whenDone != null) {
|
||||
whenDone.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe method for setting the specified status message
|
||||
*
|
||||
* @param caption The title of the tray message
|
||||
* @param text The text body of the tray message
|
||||
* @param level The message type: Level.INFO, .WARN, .SEVERE
|
||||
*/
|
||||
private void displayMessage(final String caption, final String text, final TrayIcon.MessageType level) {
|
||||
if (!headless) {
|
||||
if (tray != null) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
boolean showAllNotifications = getPref(TRAY_NOTIFICATIONS);
|
||||
if (showAllNotifications || level != TrayIcon.MessageType.INFO) {
|
||||
tray.displayMessage(caption, text, level);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log.info("{}: [{}] {}", caption, level, text);
|
||||
}
|
||||
}
|
||||
|
||||
public void singleInstanceCheck(WebsocketPorts websocketPorts) {
|
||||
// Secure
|
||||
for(int port : websocketPorts.getUnusedSecurePorts()) {
|
||||
new SingleInstanceChecker(this, port, true);
|
||||
}
|
||||
// Insecure
|
||||
for(int port : websocketPorts.getUnusedInsecurePorts()) {
|
||||
new SingleInstanceChecker(this, port, false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMonoclePreferred() {
|
||||
return getPref(TRAY_MONOCLE);
|
||||
}
|
||||
|
||||
public boolean isHeadless() {
|
||||
return headless;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get boolean user pref: Searching "user", "app" and <code>System.getProperty(...)</code>.
|
||||
*/
|
||||
private boolean getPref(ArgValue argValue) {
|
||||
return PrefsSearch.getBoolean(argValue, prefs, App.getTrayProperties());
|
||||
}
|
||||
|
||||
private void performIfIdle(int idleQualifier, ActionListener performer) {
|
||||
if (idleTimer != null) {
|
||||
idleTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
performer.actionPerformed(null);
|
||||
}
|
||||
}, idleQualifier);
|
||||
} else {
|
||||
log.warn("Idle actions have already been cleared due to activity, task not scheduled.");
|
||||
}
|
||||
}
|
||||
|
||||
public void voidIdleActions() {
|
||||
if (idleTimer != null) {
|
||||
log.trace("Not idle, stopping any actions that haven't ran yet");
|
||||
idleTimer.cancel();
|
||||
idleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
13
old code/tray/src/qz/communication/DeviceException.java
Executable file
13
old code/tray/src/qz/communication/DeviceException.java
Executable file
@@ -0,0 +1,13 @@
|
||||
package qz.communication;
|
||||
|
||||
public class DeviceException extends Exception {
|
||||
|
||||
public DeviceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DeviceException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
29
old code/tray/src/qz/communication/DeviceIO.java
Executable file
29
old code/tray/src/qz/communication/DeviceIO.java
Executable file
@@ -0,0 +1,29 @@
|
||||
package qz.communication;
|
||||
|
||||
public interface DeviceIO extends DeviceListener {
|
||||
|
||||
String getVendorId();
|
||||
|
||||
String getProductId();
|
||||
|
||||
|
||||
void open() throws DeviceException;
|
||||
|
||||
boolean isOpen();
|
||||
|
||||
void close();
|
||||
|
||||
void setStreaming(boolean streaming);
|
||||
|
||||
boolean isStreaming();
|
||||
|
||||
|
||||
byte[] readData(int responseSize, Byte exchangeConfig) throws DeviceException;
|
||||
|
||||
void sendData(byte[] data, Byte exchangeConfig) throws DeviceException;
|
||||
|
||||
|
||||
byte[] getFeatureReport(int responseSize, Byte reportId) throws DeviceException;
|
||||
|
||||
void sendFeatureReport(byte[] data, Byte reportId) throws DeviceException;
|
||||
}
|
||||
9
old code/tray/src/qz/communication/DeviceListener.java
Executable file
9
old code/tray/src/qz/communication/DeviceListener.java
Executable file
@@ -0,0 +1,9 @@
|
||||
package qz.communication;
|
||||
|
||||
public interface DeviceListener {
|
||||
/**
|
||||
* Cleanup task for when a socket closes while a device is still streaming
|
||||
*/
|
||||
void close();
|
||||
|
||||
}
|
||||
130
old code/tray/src/qz/communication/DeviceOptions.java
Executable file
130
old code/tray/src/qz/communication/DeviceOptions.java
Executable file
@@ -0,0 +1,130 @@
|
||||
package qz.communication;
|
||||
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import qz.utils.UsbUtilities;
|
||||
|
||||
public class DeviceOptions {
|
||||
|
||||
public enum DeviceMode {
|
||||
HID,
|
||||
USB,
|
||||
UNKNOWN;
|
||||
public static DeviceMode parse(String callName) {
|
||||
if (callName != null) {
|
||||
if (callName.startsWith("usb")) {
|
||||
return USB;
|
||||
} else if (callName.startsWith("hid")) {
|
||||
return HID;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceMode deviceMode;
|
||||
|
||||
private Integer vendorId;
|
||||
private Integer productId;
|
||||
|
||||
//usb specific
|
||||
private Byte interfaceId;
|
||||
private Byte endpoint;
|
||||
private int interval;
|
||||
private int responseSize;
|
||||
|
||||
//hid specific
|
||||
private Integer usagePage;
|
||||
private String serial;
|
||||
|
||||
public DeviceOptions(JSONObject parameters, DeviceMode deviceMode) {
|
||||
this.deviceMode = deviceMode;
|
||||
|
||||
vendorId = UsbUtilities.hexToInt(parameters.optString("vendorId"));
|
||||
productId = UsbUtilities.hexToInt(parameters.optString("productId"));
|
||||
|
||||
if (!parameters.isNull("interface")) {
|
||||
interfaceId = UsbUtilities.hexToByte(parameters.optString("interface"));
|
||||
}
|
||||
if (!parameters.isNull("endpoint")) {
|
||||
endpoint = UsbUtilities.hexToByte(parameters.optString("endpoint"));
|
||||
} else if (!parameters.isNull("reportId")) {
|
||||
endpoint = UsbUtilities.hexToByte(parameters.optString("reportId"));
|
||||
}
|
||||
interval = parameters.optInt("interval", 100);
|
||||
responseSize = parameters.optInt("responseSize");
|
||||
|
||||
if (!parameters.isNull("usagePage")) {
|
||||
usagePage = UsbUtilities.hexToInt(parameters.optString("usagePage"));
|
||||
}
|
||||
if (!parameters.isNull("serial")) {
|
||||
serial = parameters.optString("serial", "");
|
||||
serial = serial.isEmpty() ? null : serial;
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getVendorId() {
|
||||
return vendorId;
|
||||
}
|
||||
|
||||
public Integer getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public Byte getInterfaceId() {
|
||||
return interfaceId;
|
||||
}
|
||||
|
||||
public Byte getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public int getResponseSize() {
|
||||
return responseSize;
|
||||
}
|
||||
|
||||
public Integer getUsagePage() {
|
||||
return usagePage;
|
||||
}
|
||||
|
||||
public String getSerial() {
|
||||
return serial;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof DeviceOptions)) { return false; }
|
||||
|
||||
DeviceOptions that = (DeviceOptions)obj;
|
||||
|
||||
if (this.getVendorId().equals(that.getVendorId()) && this.getProductId().equals(that.getProductId())) {
|
||||
if (deviceMode == DeviceMode.USB
|
||||
&& (this.getInterfaceId() == null || that.getInterfaceId() == null || this.getInterfaceId().equals(that.getInterfaceId()))
|
||||
&& (this.getEndpoint() == null || that.getEndpoint() == null || this.getEndpoint().equals(that.getEndpoint()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (deviceMode == DeviceMode.HID
|
||||
&& (this.getUsagePage() == null || that.getUsagePage() == null || this.getUsagePage().equals(that.getUsagePage()))
|
||||
&& (this.getSerial() == null || that.getSerial() == null || this.getSerial().equals(that.getSerial()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder()
|
||||
.append(deviceMode)
|
||||
.append(vendorId)
|
||||
.append(productId)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
}
|
||||
173
old code/tray/src/qz/communication/FileIO.java
Executable file
173
old code/tray/src/qz/communication/FileIO.java
Executable file
@@ -0,0 +1,173 @@
|
||||
package qz.communication;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOCase;
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import qz.ws.PrintSocketClient;
|
||||
import qz.ws.StreamEvent;
|
||||
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class FileIO implements DeviceListener {
|
||||
public static final String SANDBOX_DATA_SUFFIX = "sandbox";
|
||||
public static final String GLOBAL_DATA_SUFFIX = "shared";
|
||||
|
||||
// Pesky breadcrumb files that only the OS cares about
|
||||
public static final String[] DEFAULT_EXCLUSIONS = { ".DS_Store", "Thumbs.db" };
|
||||
|
||||
public static final int FILE_LISTENER_DEFAULT_LINES = 10;
|
||||
|
||||
public enum ReadType {
|
||||
BYTES, LINES
|
||||
}
|
||||
|
||||
private Session session;
|
||||
|
||||
private Path originalPath;
|
||||
private Path absolutePath;
|
||||
|
||||
private WatchKey wk;
|
||||
|
||||
private ReadType readType;
|
||||
private boolean reversed;
|
||||
private long bytes;
|
||||
private int lines;
|
||||
|
||||
private IOCase caseSensitivity;
|
||||
|
||||
private ArrayList<String> inclusions;
|
||||
private ArrayList<String> exclusions;
|
||||
|
||||
public FileIO(Session session, JSONObject params, Path originalPath, Path absolutePath) throws JSONException {
|
||||
this.session = session;
|
||||
this.originalPath = originalPath;
|
||||
this.absolutePath = absolutePath;
|
||||
|
||||
inclusions = new ArrayList<>();
|
||||
exclusions = new ArrayList<>();
|
||||
|
||||
JSONArray inc = params.optJSONArray("include");
|
||||
JSONArray exc = params.optJSONArray("exclude");
|
||||
caseSensitivity = params.optBoolean("ignoreCase", true) ? IOCase.INSENSITIVE : IOCase.SENSITIVE;
|
||||
|
||||
if (inc != null) {
|
||||
for (int i = 0; i < inc.length(); i++) {
|
||||
inclusions.add(inc.getString(i));
|
||||
}
|
||||
}
|
||||
if (exc != null) {
|
||||
for(int i = 0; i < exc.length(); i++) {
|
||||
exclusions.add(exc.getString(i));
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject options = params.optJSONObject("listener");
|
||||
if (options != null) {
|
||||
// Setup defaults
|
||||
bytes = options.optLong("bytes", -1);
|
||||
if (bytes > 0) {
|
||||
readType = ReadType.BYTES;
|
||||
} else {
|
||||
readType = ReadType.LINES;
|
||||
}
|
||||
|
||||
lines = options.optInt("lines", readType == ReadType.LINES? FILE_LISTENER_DEFAULT_LINES:-1);
|
||||
reversed = options.optBoolean("reverse", readType == ReadType.LINES);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMatch(String fileName) {
|
||||
boolean match = inclusions.isEmpty();
|
||||
for (String inclusion : inclusions) {
|
||||
if(FilenameUtils.wildcardMatch(fileName, inclusion, caseSensitivity)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(match) {
|
||||
// Never match on DEFAULT_EXCLUSIONS
|
||||
for(String exclusion : DEFAULT_EXCLUSIONS) {
|
||||
if (FilenameUtils.wildcardMatch(fileName, exclusion, IOCase.INSENSITIVE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for(String exclusion : exclusions) {
|
||||
if (FilenameUtils.wildcardMatch(fileName, exclusion, caseSensitivity)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
public boolean returnsContents() {
|
||||
return bytes > 0 || lines > 0;
|
||||
}
|
||||
|
||||
public ReadType getReadType() {
|
||||
return readType;
|
||||
}
|
||||
|
||||
public boolean isReversed() {
|
||||
return reversed;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public int getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public Path getOriginalPath() {
|
||||
return originalPath;
|
||||
}
|
||||
|
||||
public Path getAbsolutePath() {
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
public boolean isWatching() {
|
||||
return wk != null && wk.isValid();
|
||||
}
|
||||
|
||||
public void setWk(WatchKey wk) {
|
||||
this.wk = wk;
|
||||
}
|
||||
|
||||
public void fileChanged(String fileName, String type, String fileData) throws ClosedChannelException {
|
||||
StreamEvent evt = new StreamEvent(StreamEvent.Stream.FILE, StreamEvent.Type.ACTION)
|
||||
.withData("file", getOriginalPath().resolve(fileName))
|
||||
.withData("eventType", type);
|
||||
|
||||
if (fileData != null) {
|
||||
evt.withData("fileData", fileData);
|
||||
}
|
||||
|
||||
PrintSocketClient.sendStream(session, evt);
|
||||
}
|
||||
|
||||
public void sendError(String message) throws ClosedChannelException {
|
||||
StreamEvent eventErr = new StreamEvent(StreamEvent.Stream.FILE, StreamEvent.Type.ERROR)
|
||||
.withData("message", message);
|
||||
PrintSocketClient.sendStream(session, eventErr);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (wk != null) {
|
||||
wk.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
67
old code/tray/src/qz/communication/FileParams.java
Executable file
67
old code/tray/src/qz/communication/FileParams.java
Executable file
@@ -0,0 +1,67 @@
|
||||
package qz.communication;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import qz.utils.ByteUtilities;
|
||||
import qz.utils.PrintingUtilities.Flavor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
/**
|
||||
* Created by Kyle on 2/28/2018.
|
||||
*/
|
||||
public class FileParams {
|
||||
private Path path;
|
||||
private String data;
|
||||
private Flavor flavor;
|
||||
|
||||
private boolean shared;
|
||||
private boolean sandbox;
|
||||
|
||||
private OpenOption appendMode;
|
||||
|
||||
|
||||
public FileParams(JSONObject params) throws JSONException {
|
||||
path = Paths.get(params.getString("path"));
|
||||
data = params.optString("data", "");
|
||||
flavor = Flavor.parse(params, Flavor.PLAIN);
|
||||
|
||||
shared = params.optBoolean("shared", true);
|
||||
sandbox = params.optBoolean("sandbox", true);
|
||||
|
||||
appendMode = params.optBoolean("append")? StandardOpenOption.APPEND:StandardOpenOption.TRUNCATE_EXISTING;
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String toString(byte[] bytes) {
|
||||
return ByteUtilities.toString(flavor, bytes);
|
||||
}
|
||||
|
||||
public byte[] getData() throws IOException {
|
||||
return flavor.read(data);
|
||||
}
|
||||
|
||||
public Flavor getFlavor() {
|
||||
return flavor;
|
||||
}
|
||||
|
||||
public boolean isShared() {
|
||||
return shared;
|
||||
}
|
||||
|
||||
public boolean isSandbox() {
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
public OpenOption getAppendMode() {
|
||||
return appendMode;
|
||||
}
|
||||
|
||||
}
|
||||
110
old code/tray/src/qz/communication/H4J_HidIO.java
Executable file
110
old code/tray/src/qz/communication/H4J_HidIO.java
Executable file
@@ -0,0 +1,110 @@
|
||||
package qz.communication;
|
||||
|
||||
import org.hid4java.HidDevice;
|
||||
import qz.ws.SocketConnection;
|
||||
|
||||
import javax.usb.util.UsbUtil;
|
||||
|
||||
public class H4J_HidIO implements DeviceIO, DeviceListener {
|
||||
|
||||
private HidDevice device;
|
||||
|
||||
private boolean streaming;
|
||||
|
||||
private DeviceOptions dOpts;
|
||||
private SocketConnection websocket;
|
||||
|
||||
|
||||
public H4J_HidIO(DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
|
||||
this(H4J_HidUtilities.findDevice(dOpts), dOpts, websocket);
|
||||
}
|
||||
|
||||
private H4J_HidIO(HidDevice device, DeviceOptions dOpts, SocketConnection websocket) throws DeviceException {
|
||||
this.dOpts = dOpts;
|
||||
this.websocket = websocket;
|
||||
if (device == null) {
|
||||
throw new DeviceException("HID device could not be found");
|
||||
}
|
||||
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public void open() {
|
||||
if (!isOpen()) {
|
||||
device.open();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return !device.isClosed();
|
||||
}
|
||||
|
||||
public void setStreaming(boolean active) {
|
||||
streaming = active;
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return streaming;
|
||||
}
|
||||
|
||||
public String getVendorId() {
|
||||
return UsbUtil.toHexString(device.getVendorId());
|
||||
}
|
||||
|
||||
public String getProductId() {
|
||||
return UsbUtil.toHexString(device.getProductId());
|
||||
}
|
||||
|
||||
public byte[] readData(int responseSize, Byte unused) throws DeviceException {
|
||||
byte[] response = new byte[responseSize];
|
||||
|
||||
int read = device.read(response);
|
||||
if (read == -1) {
|
||||
throw new DeviceException("Failed to read from device");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public void sendData(byte[] data, Byte reportId) throws DeviceException {
|
||||
if (reportId == null) { reportId = (byte)0x00; }
|
||||
|
||||
int wrote = device.write(data, data.length, reportId);
|
||||
if (wrote == -1) {
|
||||
throw new DeviceException("Failed to write to device");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getFeatureReport(int responseSize, Byte reportId) throws DeviceException {
|
||||
if (reportId == null) { reportId = (byte)0x00; }
|
||||
byte[] response = new byte[responseSize];
|
||||
|
||||
int read = device.getFeatureReport(response, reportId);
|
||||
if (read == -1) {
|
||||
throw new DeviceException("Failed to read from device");
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
public void sendFeatureReport(byte[] data, Byte reportId) throws DeviceException {
|
||||
if (reportId == null) { reportId = (byte)0x00; }
|
||||
|
||||
int wrote = device.sendFeatureReport(data, reportId);
|
||||
if (wrote == -1) {
|
||||
throw new DeviceException("Failed to write to device");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
setStreaming(false);
|
||||
// Remove orphaned reference
|
||||
websocket.removeDevice(dOpts);
|
||||
if (isOpen()) {
|
||||
device.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
83
old code/tray/src/qz/communication/H4J_HidListener.java
Executable file
83
old code/tray/src/qz/communication/H4J_HidListener.java
Executable file
@@ -0,0 +1,83 @@
|
||||
package qz.communication;
|
||||
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.hid4java.HidDevice;
|
||||
import org.hid4java.HidManager;
|
||||
import org.hid4java.HidServicesListener;
|
||||
import org.hid4java.event.HidServicesEvent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import qz.ws.PrintSocketClient;
|
||||
import qz.ws.StreamEvent;
|
||||
|
||||
import javax.usb.util.UsbUtil;
|
||||
|
||||
public class H4J_HidListener implements DeviceListener, HidServicesListener {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(H4J_HidListener.class);
|
||||
|
||||
private Session session;
|
||||
|
||||
|
||||
public H4J_HidListener(Session session) {
|
||||
HidManager.getHidServices().addHidServicesListener(this);
|
||||
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void hidFailure(HidServicesEvent hidServicesEvent) {
|
||||
log.debug("Device failure: {}", hidServicesEvent.getHidDevice().getProduct());
|
||||
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Failure"), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hidDataReceived(HidServicesEvent hidServicesEvent) {
|
||||
log.debug("Data received: {}", hidServicesEvent.getDataReceived().length + " bytes");
|
||||
|
||||
JSONArray hex = new JSONArray();
|
||||
for(byte b : hidServicesEvent.getDataReceived()) {
|
||||
hex.put(UsbUtil.toHexString(b));
|
||||
}
|
||||
|
||||
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Data Received", hex), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hidDeviceDetached(HidServicesEvent hidServicesEvent) {
|
||||
log.debug("Device detached: {}", hidServicesEvent.getHidDevice().getProduct());
|
||||
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Detached"), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hidDeviceAttached(HidServicesEvent hidServicesEvent) {
|
||||
log.debug("Device attached: {}", hidServicesEvent.getHidDevice().getProduct());
|
||||
PrintSocketClient.sendStream(session, createStreamAction(hidServicesEvent.getHidDevice(), "Device Attached"), this);
|
||||
}
|
||||
|
||||
private StreamEvent createStreamAction(HidDevice device, String action) {
|
||||
return createStreamAction(device, action, null);
|
||||
}
|
||||
|
||||
private StreamEvent createStreamAction(HidDevice device, String action, JSONArray dataArr) {
|
||||
StreamEvent event = new StreamEvent(StreamEvent.Stream.HID, StreamEvent.Type.ACTION)
|
||||
.withData("vendorId", UsbUtil.toHexString(device.getVendorId()))
|
||||
.withData("productId", UsbUtil.toHexString(device.getProductId()))
|
||||
.withData("actionType", action);
|
||||
|
||||
if (dataArr != null) {
|
||||
event.withData("data", dataArr);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
HidManager.getHidServices().removeHidServicesListener(this);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user