Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 95 additions & 68 deletions jdm-core/src/main/java/jdiskmark/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,24 @@ public String toString() {
*/
public enum AppIcon {
/** Blue/orange circle — the beta brand. Single resolution. */
BETA(new String[]{"/icons/icon-jdm-beta.png"}),
BETA(new String[] { "/icons/icon-jdm-beta.png" }),
/** Custom JDiskMark turtle logo — the default project brand. */
TURTLE(new String[]{
"/icons/jdm-turtle-logo-16x16.png",
"/icons/jdm-turtle-logo-20x20.png",
"/icons/jdm-turtle-logo-24x24.png",
"/icons/jdm-turtle-logo-32x32.png",
"/icons/jdm-turtle-logo-40x40.png",
"/icons/jdm-turtle-logo-48x48.png",
"/icons/jdm-turtle-logo-64x64.png",
"/icons/jdm-turtle-logo-96x96.png",
"/icons/jdm-turtle-logo-128x128.png",
"/icons/jdm-turtle-logo-256x256.png",
"/icons/jdm-turtle-logo-512x512.png",
"/icons/jdm-turtle-logo-1024x1024.png"
TURTLE(new String[] {
"/icons/jdm-turtle-logo-16x16.png",
"/icons/jdm-turtle-logo-20x20.png",
"/icons/jdm-turtle-logo-24x24.png",
"/icons/jdm-turtle-logo-32x32.png",
"/icons/jdm-turtle-logo-40x40.png",
"/icons/jdm-turtle-logo-48x48.png",
"/icons/jdm-turtle-logo-64x64.png",
"/icons/jdm-turtle-logo-96x96.png",
"/icons/jdm-turtle-logo-128x128.png",
"/icons/jdm-turtle-logo-256x256.png",
"/icons/jdm-turtle-logo-512x512.png",
"/icons/jdm-turtle-logo-1024x1024.png"
}),
/** Duke, the BSD-licensed Java mascot from the OpenJDK project. */
DUKE(new String[]{"/icons/icon-duke.png"});
DUKE(new String[] { "/icons/icon-duke.png" });

/** All resource paths for this icon variant, from smallest to largest. */
public final String[] resourcePaths;
Expand Down Expand Up @@ -243,23 +243,44 @@ public javax.swing.ImageIcon loadSize(int targetSize) {
public static String arch;
public static String processorName;
public static String jdk;
public static String username;
// PII: OS username collection removed (#117 — use anonymous or a non-PII system id instead).
// public static String username;

/**
* Stable, non-PII system identifier (32-char SHA-256 hex derived from the
* OS machine GUID / machine-id). Persisted in {@code jdm.properties} so
* it survives app restarts. See {@link UtilOs#getMachineSystemId}.
*/
public static String systemId;

// --- OS convenience helpers ---
// Delegate to UtilOs primitives. Safe to call before init() (e.g. early in
// main() or in CLI mode where App.os is never populated).

/** Returns {@code true} when running on macOS. */
public static boolean isMacOs() { return UtilOs.isMacOs(osName()); }
public static boolean isMacOs() {
return UtilOs.isMacOs(osName());
}

/** Returns {@code true} when running on Windows. */
public static boolean isWindows() { return UtilOs.isWindows(osName()); }
public static boolean isWindows() {
return UtilOs.isWindows(osName());
}

/** Returns {@code true} when running on Linux. */
public static boolean isLinux() { return UtilOs.isLinux(osName()); }
/** Resolves the OS name, falling back to the system property when {@link #os} is not yet set.
* Safe to call before {@link #init()} and in CLI mode. */
public static boolean isLinux() {
return UtilOs.isLinux(osName());
}

/**
* Resolves the OS name, falling back to the system property when {@link #os} is
* not yet set.
* Safe to call before {@link #init()} and in CLI mode.
*/
public static String osName() {
return (os != null) ? os : System.getProperty("os.name", "");
}

// benchmark options
public static Properties p;
public static File locationDir = null;
Expand All @@ -269,9 +290,11 @@ public static String osName() {
public static boolean autoSave = false;
public static boolean sharePortal = false;
// True if sharePortal was enabled in the last session; used to offer a
// one-click
// re-enable prompt at startup rather than silently resuming network activity.
// one-click re-enable prompt at startup rather than silently resuming network activity.
public static boolean sharePortalPreviouslyEnabled = false;
// True once the user has answered the first-run portal-consent prompt.
// Persisted so the prompt is shown exactly once (issue #117).
public static boolean portalConsentAsked = false;
public static boolean verbose = false; // affects cli output
public static boolean multiFile = true;
public static boolean autoRemoveData = true;
Expand Down Expand Up @@ -393,13 +416,11 @@ public static void init() {

GcDetector.printActive();

username = System.getProperty("user.name");

os = System.getProperty("os.name");
arch = System.getProperty("os.arch");
processorName = Util.getProcessorName();
jdk = Util.getJvmInfo();

checkPermission();
if (!APP_CACHE_DIR.exists()) {
APP_CACHE_DIR.mkdirs();
Expand All @@ -409,6 +430,14 @@ public static void init() {
loadConfig();
}

// Derive the stable, non-PII machine identifier now that loadConfig() has
// populated the persisted fallback value (if any). Resolved after loadConfig
// so we never clobber portalConsentAsked or other flags with a premature
// saveConfig() call.
String fallbackSystemId = (systemId != null && !systemId.isBlank()) ? systemId : "";
systemId = UtilOs.getMachineSystemId(os, fallbackSystemId);
// systemId persisted by the shutdown-hook saveConfig() and other normal save paths.

// initialize data dir if necessary
if (locationDir == null) {
locationDir = new File(System.getProperty("user.home"));
Expand Down Expand Up @@ -437,11 +466,23 @@ public void run() {
App.saveConfig();
}
});
// If portal upload was active last session, offer a one-click re-enable.
// This avoids silent outbound network activity while keeping dev workflow
// smooth.
if (sharePortalPreviouslyEnabled) {
javax.swing.SwingUtilities.invokeLater(App::promptResumePortalUpload);
// #117 First-run consent: ask once if the user has never been asked.
// Fires for both test and production endpoints so the dialog can be
// exercised from the IDE without any config changes.
// This runs before the re-enable check so a brand-new install shows
// the consent dialog rather than nothing.
if (!portalConsentAsked) {
javax.swing.SwingUtilities.invokeLater(Gui::promptFirstRunPortalConsent);
} else if (sharePortalPreviouslyEnabled) {
// Consent was already given and upload was active last session —
// silently restore it. No need to ask again once consent is on record.
sharePortal = true;
javax.swing.SwingUtilities.invokeLater(() -> {
msg("Portal upload active — disable via the Sharing tab.");
if (Gui.mainFrame != null) {
Gui.mainFrame.loadPropertiesConfig();
}
});
}
}
}
Expand All @@ -450,13 +491,15 @@ public void run() {
* Attempts to acquire an OS-level advisory lock on a file in the per-version
* cache directory. Called once at startup in GUI mode, before {@link #init()}.
*
* <p>The lock is held by a {@link java.nio.channels.FileLock} whose lifecycle
* <p>
* The lock is held by a {@link java.nio.channels.FileLock} whose lifecycle
* is tied to the JVM process: the OS kernel releases it automatically when the
* process exits by <em>any</em> means (normal exit, uncaught exception,
* {@code SIGKILL}, OOM crash). Stale lock files left behind after a crash are
* therefore impossible — the next launch will always succeed.
*
* <p>If another instance already holds the lock a user-friendly dialog is shown
* <p>
* If another instance already holds the lock a user-friendly dialog is shown
* and the method returns {@code false}, allowing {@code main()} to exit cleanly
* without opening any window or touching the Derby database.
*
Expand Down Expand Up @@ -498,7 +541,7 @@ public static boolean acquireInstanceLock() {
javax.swing.JOptionPane.showMessageDialog(
null,
"JDiskMark is already running.\n"
+ "Only one instance can be open at a time.",
+ "Only one instance can be open at a time.",
"JDiskMark — Already Running",
javax.swing.JOptionPane.WARNING_MESSAGE);
System.exit(0);
Expand All @@ -521,35 +564,6 @@ public static void checkPermission() {
}
}

/**
* Offers a one-click prompt to re-enable portal upload when it was active
* in the previous session. Called after the main window is visible so the
* dialog has a proper parent. This avoids silent outbound network activity
* while keeping the dev workflow convenient (no password re-entry required).
*/
public static void promptResumePortalUpload() {
int choice = javax.swing.JOptionPane.showConfirmDialog(
Gui.mainFrame,
"Portal upload was enabled in your last session.\nResume uploading benchmarks to "
+ Portal.getUploadUrl() + "?",
"Resume Portal Upload?",
javax.swing.JOptionPane.YES_NO_OPTION,
javax.swing.JOptionPane.QUESTION_MESSAGE);
if (choice == javax.swing.JOptionPane.YES_OPTION) {
sharePortal = true;
msg("Portal upload resumed.");
} else {
sharePortal = false;
sharePortalPreviouslyEnabled = false; // clear so we don't prompt again next launch
msg("Portal upload not resumed.");
saveConfig(); // persist the cleared state
}
// sync the menu checkbox to reflect the resolved state
if (Gui.mainFrame != null) {
Gui.mainFrame.loadPropertiesConfig();
}
}

public static void loadProfile(BenchmarkProfile profile) {
try {
activeProfile = profile;
Expand Down Expand Up @@ -595,13 +609,18 @@ public static void loadConfig() {
// configure settings from properties
String value;

// Never silently re-enable portal upload on startup — network activity must
// always be explicitly user-confirmed each session. We remember the previous
// state only to offer a convenient one-click re-enable prompt.
// Remember previous state only to offer convenient one-click re-enable prompt.
value = p.getProperty("sharePortal", "false");
sharePortalPreviouslyEnabled = Boolean.parseBoolean(value);
sharePortal = false; // always start disabled; prompt offered after window visible

// #117 one-time first-run consent flag
value = p.getProperty("portalConsentAsked", "false");
portalConsentAsked = Boolean.parseBoolean(value);

// Non-PII system identifier (blank on very first run; resolved in init())
systemId = p.getProperty("systemId", "");

Portal.uploadResourceLocator = p.getProperty("uploadResourceLocator", Portal.uploadResourceLocator);
Portal.uploadProtocol = p.getProperty("uploadProtocol", Portal.uploadProtocol);

Expand Down Expand Up @@ -709,8 +728,16 @@ public static void saveConfig() {

// configure properties
p.setProperty("sharePortal", String.valueOf(sharePortal));
p.setProperty("uploadResourceLocator", Portal.uploadResourceLocator);
p.setProperty("uploadProtocol", Portal.uploadProtocol);
p.setProperty("portalConsentAsked", String.valueOf(portalConsentAsked)); // #117
if (systemId != null && !systemId.isBlank()) {
p.setProperty("systemId", systemId);
}
if (Portal.uploadResourceLocator != null) {
p.setProperty("uploadResourceLocator", Portal.uploadResourceLocator);
}
if (Portal.uploadProtocol != null) {
p.setProperty("uploadProtocol", Portal.uploadProtocol);
}
p.setProperty("activeProfile", activeProfile.name());
p.setProperty("profileModified", String.valueOf(profileModified));
p.setProperty("benchmarkType", benchmarkType.name());
Expand Down
17 changes: 14 additions & 3 deletions jdm-core/src/main/java/jdiskmark/Benchmark.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,21 @@ public void serialize(UUID value, JsonGenerator gen, SerializerProvider serializ
@JsonSerialize(using = UuidToMongoIdSerializer.class)
private UUID id;

// user account
@Column
// PII: #117 set only after a user elects to login to access their profile
// @Column
String username = "anonymous"; // "user" is reserved in Derby
public String getUsername() { return username; }
// public String getUsername() { return username; }
public String getUsername() { return "anonymous"; }

/**
* Non-PII stable system identifier derived from the OS machine GUID / machine-id.
* 32-char lowercase SHA-256 hex. Set from {@link App#systemId} at benchmark
* creation time and included in every portal upload payload.
* See {@link UtilOs#getMachineSystemId} for the derivation strategy.
*/
@Column
String systemId = "";
public String getSystemId() { return systemId; }

// system info
@Embedded
Expand Down
4 changes: 2 additions & 2 deletions jdm-core/src/main/java/jdiskmark/BenchmarkRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ private BenchmarkOperation createOp(Benchmark b, IOMode mode) {
}

private void mapEnvironment(Benchmark b, String model, String partId, DiskUsageInfo u) {
b.username = App.username;
b.systemId = (App.systemId != null) ? App.systemId : "";

b.systemInfo.processorName = App.processorName;
b.systemInfo.os = App.os;
b.systemInfo.arch = App.arch;
Expand Down
Loading
Loading