From 54bb0fd44177b39fcf7bf703714b0a7d98c09475 Mon Sep 17 00:00:00 2001 From: AjinkyaGokhale Date: Fri, 22 May 2026 15:24:29 +0200 Subject: [PATCH 1/2] ci: add CI workflow, SpotBugs, and release drafter --- .github/release-drafter.yml | 33 +++++++++++++++++++++++++++ .github/workflows/ci.yml | 20 ++++++++++++++++ .github/workflows/release-drafter.yml | 19 +++++++++++++++ pom.xml | 17 ++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..195f2c1 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,33 @@ +name-template: 'ESP Flasher v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +categories: + - title: 'New Features' + labels: [ 'feat', 'feature', 'enhancement' ] + - title: 'Bug Fixes' + labels: [ 'fix', 'bug', 'bugfix' ] + - title: 'Performance' + labels: [ 'perf', 'performance' ] + - title: 'Maintenance' + labels: [ 'chore', 'deps', 'refactor', 'ci' ] + - title: 'Documentation' + labels: [ 'docs', 'documentation' ] + +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' + +version-resolver: + major: + labels: [ 'breaking', 'major' ] + minor: + labels: [ 'feat', 'feature', 'enhancement' ] + patch: + labels: [ 'fix', 'bug', 'bugfix', 'perf', 'chore', 'deps', 'docs', 'ci' ] + default: patch + +template: | + ## What's Changed + + $CHANGES + + **Full Changelog**: https://github.com/AjinkyaGokhale/esp-flasher-java/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..72ab6dd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [ main, master, dev ] + pull_request: + branches: [ main, master, dev ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'zulu' + cache: 'maven' + - name: Build, test, and analyse + run: ./mvnw -B verify diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..2e9db90 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,19 @@ +name: Release Drafter + +on: + push: + branches: [ main, master ] + pull_request: + types: [ opened, reopened, synchronize ] + +permissions: + contents: write + pull-requests: write + +jobs: + update-release-draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pom.xml b/pom.xml index 0a421c9..33a4159 100644 --- a/pom.xml +++ b/pom.xml @@ -197,6 +197,23 @@ + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.4 + + Default + Medium + true + + + + check + + + + org.panteleyev From cf0246cb6e41b66b87c6de6aa067751242d1af50 Mon Sep 17 00:00:00 2001 From: AjinkyaGokhale Date: Fri, 22 May 2026 15:30:11 +0200 Subject: [PATCH 2/2] fix: resolve all 16 SpotBugs warnings --- pom.xml | 8 ++++++++ .../espflasher/service/EsptoolRunner.java | 3 ++- .../espflasher/service/FlashLogger.java | 11 +++++++++-- .../espflasher/service/PrereqChecker.java | 12 ++++++++---- .../espflasher/service/SettingsManager.java | 8 ++++++-- .../espflasher/service/UpdateService.java | 10 +++++++--- .../com/ajinkyagokhale/espflasher/ui/FlasherApp.java | 8 ++++++-- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 33a4159..17e7ae2 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,14 @@ 5.14.0 + + + com.github.spotbugs + spotbugs-annotations + 4.8.6 + compile + + org.junit.jupiter diff --git a/src/main/java/com/ajinkyagokhale/espflasher/service/EsptoolRunner.java b/src/main/java/com/ajinkyagokhale/espflasher/service/EsptoolRunner.java index bde5796..55d5ac2 100644 --- a/src/main/java/com/ajinkyagokhale/espflasher/service/EsptoolRunner.java +++ b/src/main/java/com/ajinkyagokhale/espflasher/service/EsptoolRunner.java @@ -6,6 +6,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -40,7 +41,7 @@ public void startFlashing(FlashConfig config, FlashListener listener) { Pattern pattern = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*%"); try (BufferedReader reader = new BufferedReader( - new InputStreamReader(currentProcess.getInputStream()))) { + new InputStreamReader(currentProcess.getInputStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { if (isCancelled) break; diff --git a/src/main/java/com/ajinkyagokhale/espflasher/service/FlashLogger.java b/src/main/java/com/ajinkyagokhale/espflasher/service/FlashLogger.java index 38f9082..2125b52 100644 --- a/src/main/java/com/ajinkyagokhale/espflasher/service/FlashLogger.java +++ b/src/main/java/com/ajinkyagokhale/espflasher/service/FlashLogger.java @@ -3,10 +3,13 @@ import com.ajinkyagokhale.espflasher.model.AppSettings; import com.ajinkyagokhale.espflasher.model.FlashResult; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; public class FlashLogger { @@ -14,6 +17,7 @@ public class FlashLogger { private final AppSettings settings; + @SuppressFBWarnings("EI_EXPOSE_REP2") public FlashLogger(AppSettings settings) { this.settings = settings; } @@ -22,12 +26,15 @@ public void append(FlashResult result) { if (!settings.isLogFileEnabled()) return; File dir = new File(settings.getLogFilePath()); - if (!dir.exists()) dir.mkdirs(); + if (!dir.exists() && !dir.mkdirs()) { + System.err.println("Failed to create log directory: " + dir); + return; + } File logFile = new File(dir, "flash-log.csv"); boolean writeHeader = !logFile.exists() || logFile.length() == 0; - try (PrintWriter pw = new PrintWriter(new FileWriter(logFile, true))) { + try (PrintWriter pw = new PrintWriter(new FileWriter(logFile, StandardCharsets.UTF_8, true))) { if (writeHeader) pw.println(HEADER); pw.printf("%s,%s,%s,%s,\"%s\"%n", result.getTimeStamp(), diff --git a/src/main/java/com/ajinkyagokhale/espflasher/service/PrereqChecker.java b/src/main/java/com/ajinkyagokhale/espflasher/service/PrereqChecker.java index 6e2f105..c07b879 100644 --- a/src/main/java/com/ajinkyagokhale/espflasher/service/PrereqChecker.java +++ b/src/main/java/com/ajinkyagokhale/espflasher/service/PrereqChecker.java @@ -1,7 +1,9 @@ package com.ajinkyagokhale.espflasher.service; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -164,7 +166,7 @@ public boolean installEsptool(Consumer onLine) { .redirectErrorStream(true) .start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { if (onLine != null) onLine.accept(line); @@ -173,7 +175,8 @@ public boolean installEsptool(Consumer onLine) { p.waitFor(); checkAll(); return esptoolCmd != null; - } catch (Exception e) { + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); return false; } } @@ -182,10 +185,11 @@ private String runCommand(String... args) { Process p = new ProcessBuilder(args) .redirectErrorStream(true) .start(); - String output = new String(p.getInputStream().readAllBytes()).strip(); + String output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8).strip(); int exit = p.waitFor(); return (exit == 0 && !output.isEmpty()) ? output : null; - } catch (Exception e) { + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); return null; } } diff --git a/src/main/java/com/ajinkyagokhale/espflasher/service/SettingsManager.java b/src/main/java/com/ajinkyagokhale/espflasher/service/SettingsManager.java index 294f364..4e65824 100644 --- a/src/main/java/com/ajinkyagokhale/espflasher/service/SettingsManager.java +++ b/src/main/java/com/ajinkyagokhale/espflasher/service/SettingsManager.java @@ -1,6 +1,7 @@ package com.ajinkyagokhale.espflasher.service; import com.ajinkyagokhale.espflasher.model.AppSettings; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import tools.jackson.databind.ObjectMapper; import java.io.File; @@ -23,8 +24,9 @@ public void save() { try { // create directory if doesn't exist File dir = new File(SETTINGS_DIR); - if (!dir.exists()) { - dir.mkdirs(); + if (!dir.exists() && !dir.mkdirs()) { + System.err.println("Failed to create settings directory: " + dir); + return; } mapper.writerWithDefaultPrettyPrinter() @@ -35,6 +37,7 @@ public void save() { } } + @SuppressFBWarnings("EI_EXPOSE_REP") public AppSettings load() { try { File file = new File(SETTINGS_FILE); @@ -48,6 +51,7 @@ public AppSettings load() { return settings; } + @SuppressFBWarnings("EI_EXPOSE_REP") public AppSettings getSettings() { return settings; } diff --git a/src/main/java/com/ajinkyagokhale/espflasher/service/UpdateService.java b/src/main/java/com/ajinkyagokhale/espflasher/service/UpdateService.java index 3740542..2a6603b 100644 --- a/src/main/java/com/ajinkyagokhale/espflasher/service/UpdateService.java +++ b/src/main/java/com/ajinkyagokhale/espflasher/service/UpdateService.java @@ -1,9 +1,11 @@ package com.ajinkyagokhale.espflasher.service; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.json.JSONArray; import org.json.JSONObject; import java.awt.Desktop; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; @@ -39,7 +41,7 @@ public String currentVersion() { Properties props = new Properties(); props.load(in); return props.getProperty("version", "0.0.0").trim(); - } catch (Exception e) { + } catch (IOException e) { return "0.0.0"; } } @@ -71,7 +73,8 @@ public Optional latestRelease() { } } return Optional.empty(); - } catch (Exception e) { + } catch (IOException | InterruptedException e) { + Thread.currentThread().interrupt(); return Optional.empty(); } } @@ -88,6 +91,7 @@ public boolean isNewer(String latest, String current) { return false; } + @SuppressFBWarnings("DM_EXIT") public boolean downloadAndLaunch(Release release, ProgressListener listener, AtomicBoolean cancelled) throws Exception { String ext = assetExtension(); @@ -126,7 +130,7 @@ public boolean downloadAndLaunch(Release release, ProgressListener listener, Ato return false; } Desktop.getDesktop().open(target.toFile()); - System.exit(0); + System.exit(0); // intentional: installer takes over, JVM must exit return true; } diff --git a/src/main/java/com/ajinkyagokhale/espflasher/ui/FlasherApp.java b/src/main/java/com/ajinkyagokhale/espflasher/ui/FlasherApp.java index 9b93bec..544727f 100644 --- a/src/main/java/com/ajinkyagokhale/espflasher/ui/FlasherApp.java +++ b/src/main/java/com/ajinkyagokhale/espflasher/ui/FlasherApp.java @@ -6,6 +6,7 @@ import com.ajinkyagokhale.espflasher.model.FlashConfig; import com.ajinkyagokhale.espflasher.model.FlashResult; import com.ajinkyagokhale.espflasher.service.*; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javafx.application.Application; import javafx.application.Platform; @@ -29,6 +30,7 @@ import java.awt.*; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -852,7 +854,7 @@ private boolean isDarkMode() { Process p = Runtime.getRuntime().exec( new String[]{"defaults", "read", "-g", "AppleInterfaceStyle"} ); - String result = new String(p.getInputStream().readAllBytes()).strip(); + String result = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8).strip(); return result.equalsIgnoreCase("dark"); } else if (os.contains("windows")) { Process p = Runtime.getRuntime().exec(new String[]{ @@ -860,7 +862,7 @@ private boolean isDarkMode() { "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "/v", "AppsUseLightTheme" }); - String result = new String(p.getInputStream().readAllBytes()); + String result = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8); return result.contains("0x0"); } } catch (Exception e) { @@ -1130,6 +1132,7 @@ void DwmSetWindowAttribute(com.sun.jna.platform.win32.WinDef.HWND hwnd, int attr com.sun.jna.ptr.IntByReference value, int size); } + @SuppressFBWarnings("DE_MIGHT_IGNORE") private void applyDarkTitleBar(Stage stage) { if (!System.getProperty("os.name", "").toLowerCase().contains("win")) return; try { @@ -1155,6 +1158,7 @@ private void checkForUpdates() { worker.start(); } + @SuppressFBWarnings("DM_EXIT") private void promptForcedUpdate(UpdateService updates, UpdateService.Release release, String current) { ButtonType updateNow = new ButtonType("Update Now", ButtonBar.ButtonData.OK_DONE); ButtonType quit = new ButtonType("Quit", ButtonBar.ButtonData.CANCEL_CLOSE);