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..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
@@ -197,6 +205,23 @@
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.8.6.4
+
+ Default
+ Medium
+ true
+
+
+
+ check
+
+
+
+
org.panteleyev
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);