diff --git a/src/main/java/org/qora/controller/Controller.java b/src/main/java/org/qora/controller/Controller.java index c8561eed..958b7d2e 100644 --- a/src/main/java/org/qora/controller/Controller.java +++ b/src/main/java/org/qora/controller/Controller.java @@ -1,5 +1,6 @@ package org.qora.controller; +import java.awt.TrayIcon.MessageType; import java.io.IOException; import java.io.InputStream; import java.security.SecureRandom; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; +import java.util.Scanner; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; @@ -33,6 +35,7 @@ import org.qora.data.block.BlockSummaryData; import org.qora.data.network.PeerData; import org.qora.data.transaction.ArbitraryTransactionData; import org.qora.data.transaction.ArbitraryTransactionData.DataType; +import org.qora.globalization.Translator; import org.qora.data.transaction.TransactionData; import org.qora.gui.Gui; import org.qora.gui.SysTray; @@ -87,6 +90,7 @@ public class Controller extends Thread { private static final String repositoryUrlTemplate = "jdbc:hsqldb:file:%s/blockchain;create=true;hsqldb.full_log_replay=true"; private static final long ARBITRARY_REQUEST_TIMEOUT = 5 * 1000; // ms private static final long REPOSITORY_BACKUP_PERIOD = 123 * 60 * 1000; // ms + private static final long NTP_NAG_PERIOD = 5 * 60 * 1000; // ms private static volatile boolean isStopping = false; private static BlockGenerator blockGenerator = null; @@ -97,6 +101,7 @@ public class Controller extends Thread { private final long buildTimestamp; // seconds private long repositoryBackupTimestamp = startTime + REPOSITORY_BACKUP_PERIOD; + private long ntpNagTimestamp = startTime + NTP_NAG_PERIOD; /** * Map of recent requests for ARBITRARY transaction data payloads. @@ -307,6 +312,12 @@ public class Controller extends Thread { repositoryBackupTimestamp += REPOSITORY_BACKUP_PERIOD; RepositoryManager.backup(true); } + + // Potentially nag end-user about NTP + if (System.currentTimeMillis() >= ntpNagTimestamp) { + ntpNagTimestamp += NTP_NAG_PERIOD; + ntpNag(); + } } } catch (InterruptedException e) { // Fall-through to exit @@ -383,6 +394,48 @@ public class Controller extends Thread { } } + /** Nag Windows users that don't have many/any peers and not using Windows' auto time sync. */ + private void ntpNag() { + // Only for Windows users + if (!System.getProperty("os.name").toLowerCase().contains("win")) + return; + + // Suffering from lack of peers? + final int numberOfPeers = Network.getInstance().getUniqueHandshakedPeers().size(); + if (numberOfPeers > 0) + return; + + // Do we actually know any peers to connect to? + try (final Repository repository = RepositoryManager.getRepository()) { + final int numberOfKnownPeers = repository.getNetworkRepository().getAllPeers().size(); + if (numberOfKnownPeers == 0) + return; + } catch (DataException e) { + // Not important + return; + } + + String[] detectCmd = new String[] { "net", "start" }; + try { + Process process = new ProcessBuilder(Arrays.asList(detectCmd)).start(); + try (InputStream in = process.getInputStream(); Scanner scanner = new Scanner(in, "UTF8")) { + scanner.useDelimiter("\\A"); + String output = scanner.hasNext() ? scanner.next() : ""; + boolean isRunning = output.contains("Windows Time"); + if (isRunning) + return; + } + } catch (IOException e) { + // Not important + return; + } + + // Time to nag + String caption = Translator.INSTANCE.translate("SysTray", "NTP_NAG_CAPTION"); + String text = Translator.INSTANCE.translate("SysTray", "NTP_NAG_TEXT"); + SysTray.getInstance().showMessage(caption, text, MessageType.INFO); + } + public void updateSysTray() { final int numberOfPeers = Network.getInstance().getUniqueHandshakedPeers().size(); diff --git a/src/main/resources/i18n/SysTray_en.properties b/src/main/resources/i18n/SysTray_en.properties new file mode 100644 index 00000000..2f74f744 --- /dev/null +++ b/src/main/resources/i18n/SysTray_en.properties @@ -0,0 +1,3 @@ +# Nagging about lack of NTP time sync +NTP_NAG_CAPTION=No connections? +NTP_NAG_TEXT=Please enable Windows automatic time synchronization diff --git a/src/main/resources/i18n/SysTray_zh.properties b/src/main/resources/i18n/SysTray_zh.properties new file mode 100644 index 00000000..2f74f744 --- /dev/null +++ b/src/main/resources/i18n/SysTray_zh.properties @@ -0,0 +1,3 @@ +# Nagging about lack of NTP time sync +NTP_NAG_CAPTION=No connections? +NTP_NAG_TEXT=Please enable Windows automatic time synchronization diff --git a/src/test/java/org/qora/test/WindowsTimeServiceTests.java b/src/test/java/org/qora/test/WindowsTimeServiceTests.java new file mode 100644 index 00000000..ba471124 --- /dev/null +++ b/src/test/java/org/qora/test/WindowsTimeServiceTests.java @@ -0,0 +1,49 @@ +package org.qora.test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Scanner; + +public class WindowsTimeServiceTests { + + public static void main(String[] args) { + System.out.println("Detecting Windows Time Service..."); + String[] detectCmd = new String[] { "net", "start" }; + try { + Process process = new ProcessBuilder(Arrays.asList(detectCmd)).start(); + try (InputStream in = process.getInputStream(); Scanner scanner = new Scanner(in, "UTF8")) { + scanner.useDelimiter("\\A"); + String output = scanner.hasNext() ? scanner.next() : ""; + boolean isRunning = output.contains("Windows Time"); + + System.out.println(String.format("Windows Time Service running: %s", isRunning ? "yes" : "no")); + } + int exitStatus = process.waitFor(); + System.out.println(String.format("Exit status: %d", exitStatus)); + } catch (IOException | InterruptedException e) { + System.err.println(String.format("Failed to detect Windows Time Service: %s", e.getMessage())); + } + + System.out.println("Starting Windows Time Service..."); + String[] startCmd = new String[] { "net", "start", "w32time" }; + try { + Process process = new ProcessBuilder(Arrays.asList(startCmd)).start(); + int exitStatus = process.waitFor(); + System.out.println(String.format("Exit status: %d", exitStatus)); + } catch (IOException | InterruptedException e) { + System.err.println(String.format("Failed to start Windows Time Service: %s", e.getMessage())); + } + + System.out.println("Force syncing Windows Time Service..."); + String[] resyncCmd = new String[] { "w32tm", "/resync" }; + try { + Process process = new ProcessBuilder(Arrays.asList(resyncCmd)).start(); + int exitStatus = process.waitFor(); + System.out.println(String.format("Exit status: %d", exitStatus)); + } catch (IOException | InterruptedException e) { + System.err.println(String.format("Failed to force sync Windows Time Service: %s", e.getMessage())); + } + } + +}