diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 652f1eb77..cc08fc1f4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,10 +23,6 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -**Espressif-IDE Product Information:** -Espressif > Product Information (Copy content from the console and attach as a file) - -**Eclipse Error log:** -Window > Show View > Other > Search for "Error Log" (Attach as a file) - -Please attach the error log as described here https://github.com/espressif/idf-eclipse-plugin#error-log +**Bug Report** +Please attach the bug report zip file generated. The file is generated in the workspace root directory. +Espressif > Generate Bug Report \ No newline at end of file diff --git a/bundles/com.espressif.idf.core/META-INF/MANIFEST.MF b/bundles/com.espressif.idf.core/META-INF/MANIFEST.MF index b714fd244..05973eecf 100644 --- a/bundles/com.espressif.idf.core/META-INF/MANIFEST.MF +++ b/bundles/com.espressif.idf.core/META-INF/MANIFEST.MF @@ -38,6 +38,7 @@ Automatic-Module-Name: com.espressif.idf.core Bundle-ActivationPolicy: lazy Export-Package: com.espressif.idf.core, com.espressif.idf.core.actions, + com.espressif.idf.core.bug, com.espressif.idf.core.build, com.espressif.idf.core.configparser, com.espressif.idf.core.configparser.vo, diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/bug/BugReportGenerator.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/bug/BugReportGenerator.java new file mode 100644 index 000000000..421769758 --- /dev/null +++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/bug/BugReportGenerator.java @@ -0,0 +1,459 @@ +/******************************************************************************* + * Copyright 2025 Espressif Systems (Shanghai) PTE LTD. + * All rights reserved. Use is subject to license terms. + *******************************************************************************/ +package com.espressif.idf.core.bug; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; + +import com.espressif.idf.core.IDFConstants; +import com.espressif.idf.core.IDFCorePlugin; +import com.espressif.idf.core.IDFCorePreferenceConstants; +import com.espressif.idf.core.IDFEnvironmentVariables; +import com.espressif.idf.core.ProcessBuilderFactory; +import com.espressif.idf.core.SystemExecutableFinder; +import com.espressif.idf.core.logging.Logger; +import com.espressif.idf.core.util.FileUtil; +import com.espressif.idf.core.util.IDFUtil; +import com.espressif.idf.core.util.StringUtil; + +/** + * This class generates a bug report zip file containing: 1. Installed tools information 2. Product information 3. Basic + * system information (OS, Arch, Memory) 4. IDE metadata logs 5. eim logs (if available) + * + * The generated zip file is named with a timestamp and stored in the workspace directory. + * + * @author Ali Azam Rana + * + */ +public class BugReportGenerator +{ + private static final String JAVA_RUNTIME_VERSION_MSG = "Java Runtime Version:"; //$NON-NLS-1$ + private static final String OPERATING_SYSTEM_MSG = "Operating System:"; //$NON-NLS-1$ + private static final String ECLIPSE_CDT_MSG = "Eclipse CDT Version:"; //$NON-NLS-1$ + private static final String IDF_ECLIPSE_PLUGIN_VERSION_MSG = "IDF Eclipse Plugin Version:"; //$NON-NLS-1$ + private static final String ECLIPSE_VERSION_MSG = "Eclipse Version:"; //$NON-NLS-1$ + private static final String PYTHON_IDF_ENV_MSG = "Python set for IDF_PYTHON_ENV:"; //$NON-NLS-1$ + private static final String NOT_FOUND_MSG = ""; //$NON-NLS-1$ + + private static final String ECLIPSE_LOG_FILE_NAME = ".log"; //$NON-NLS-1$ + private static final String ECLIPSE_METADATA_DIRECTORY = ".metadata"; //$NON-NLS-1$ + private static final String UNKNOWN = "Unknown"; //$NON-NLS-1$ + private static final String BUG_REPORT_DIRECTORY_PREFIX = "bug_report_"; //$NON-NLS-1$ + private File bugReportDirectory; + + private enum ByteUnit + { + B("B"), //$NON-NLS-1$ + KB("KB"), //$NON-NLS-1$ + MB("MB"), //$NON-NLS-1$ + GB("GB"), //$NON-NLS-1$ + TB("TB"), //$NON-NLS-1$ + PB("PB"); //$NON-NLS-1$ + + final String label; + + ByteUnit(String label) + { + this.label = label; + } + + ByteUnit next() + { + int i = ordinal(); + return i < PB.ordinal() ? values()[i + 1] : PB; + } + } + + public BugReportGenerator() + { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); //$NON-NLS-1$ + bugReportDirectory = getWorkspaceDirectory().resolve(BUG_REPORT_DIRECTORY_PREFIX + timestamp + File.separator) + .toFile(); + } + + private File getEimLogPath() + { + String eimPath = StringUtil.EMPTY; + switch (Platform.getOS()) + { + case Platform.OS_WIN32: + eimPath = System.getenv("LOCALAPPDATA"); //$NON-NLS-1$ + if (!StringUtil.isEmpty(eimPath)) + { + eimPath = eimPath + "\\eim\\logs"; //$NON-NLS-1$ + } + break; + case Platform.OS_MACOSX: + eimPath = System.getProperty("user.home"); //$NON-NLS-1$ + if (!StringUtil.isEmpty(eimPath)) + { + eimPath = eimPath + "/Library/Application Support/eim/logs"; //$NON-NLS-1$ + } + break; + case Platform.OS_LINUX: + eimPath = System.getProperty("user.home"); //$NON-NLS-1$ + if (!StringUtil.isEmpty(eimPath)) + { + eimPath = eimPath + "/.local/share/.eim/logs"; //$NON-NLS-1$ + } + break; + default: + break; + } + + return new File(eimPath); + } + + private Path getWorkspaceDirectory() + { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + File workspaceRoot = workspace.getRoot().getLocation().toFile(); + return workspaceRoot.toPath(); + } + + private List getIdeMetadataLogsFile() + { + File metadataDir = getWorkspaceDirectory().resolve(ECLIPSE_METADATA_DIRECTORY).toFile(); + File[] allFiles = metadataDir.listFiles(); + List logFiles = new LinkedList<>(); + if (!metadataDir.exists() || !metadataDir.isDirectory() || allFiles == null) + { + return logFiles; + } + File activeLog = new File(metadataDir, ECLIPSE_LOG_FILE_NAME); + LocalDate refDate = null; + if (activeLog.exists() && activeLog.isFile()) + { + refDate = Instant.ofEpochMilli(activeLog.lastModified()).atZone(ZoneId.systemDefault()).toLocalDate(); + } + + for (File file : allFiles) + { + if (file.isDirectory()) + { + continue; + } + + String fileName = file.getName(); + if (fileName.equals("version.ini")) //$NON-NLS-1$ + { + logFiles.add(file); + continue; + } + + if (fileName.endsWith(ECLIPSE_LOG_FILE_NAME)) + { + if (fileName.equals(ECLIPSE_LOG_FILE_NAME)) + { + logFiles.add(file); + continue; + } + + // Including only same day log files or one day earlier to ignore any late night logs. + if (refDate != null) + { + LocalDate fileDate = Instant.ofEpochMilli(file.lastModified()).atZone(ZoneId.systemDefault()) + .toLocalDate(); + + if (fileDate.equals(refDate) || fileDate.equals(refDate.minusDays(1))) + { + logFiles.add(file); + } + } + + } + } + + return logFiles; + } + + private File createBasicSystemInfoFile() throws IOException + { + String osName = System.getProperty("os.name", UNKNOWN); //$NON-NLS-1$ + String osVersion = System.getProperty("os.version", UNKNOWN); //$NON-NLS-1$ + String arch = System.getProperty("os.arch", UNKNOWN); //$NON-NLS-1$ + + long freePhys = -1L; + long totalPhys = -1L; + try + { + com.sun.management.OperatingSystemMXBean osBean = (com.sun.management.OperatingSystemMXBean) ManagementFactory + .getOperatingSystemMXBean(); + freePhys = osBean.getFreeMemorySize(); + totalPhys = osBean.getTotalMemorySize(); + } + catch (Throwable t) + { + // jdk.management module not present or different VM; leave values as -1. + } + + StringBuilder sb = new StringBuilder(); + sb.append("Basic System Info").append(System.lineSeparator()); //$NON-NLS-1$ + sb.append("==================").append(System.lineSeparator()); //$NON-NLS-1$ + sb.append("Generated: ").append(LocalDateTime.now()).append(System.lineSeparator()); //$NON-NLS-1$ + sb.append("OS : ").append(osName).append(" ").append(osVersion).append(System.lineSeparator()); //$NON-NLS-1$ //$NON-NLS-2$ + sb.append("Arch : ").append(arch).append(System.lineSeparator()); //$NON-NLS-1$ + if (totalPhys >= 0 && freePhys >= 0) + { + long used = totalPhys - freePhys; + sb.append("Memory :").append(System.lineSeparator()); //$NON-NLS-1$ + sb.append(" Total : ").append(humanBytes(totalPhys)).append(" (").append(totalPhys) //$NON-NLS-1$ //$NON-NLS-2$ + .append(" bytes)").append(System.lineSeparator()); //$NON-NLS-1$ + sb.append(" Available : ").append(humanBytes(freePhys)).append(" (").append(freePhys).append(" bytes)") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + .append(System.lineSeparator()); + sb.append(" In Use : ").append(humanBytes(used)).append(" (").append(used).append(" bytes)") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + .append(System.lineSeparator()); + } + else + { + sb.append("Memory : Unavailable (OS-level physical memory query unsupported on this JVM)") //$NON-NLS-1$ + .append(System.lineSeparator()); + } + + // Use your existing helper that writes content to a temp file with the given name. + // This will create a file named exactly "basic_system_info". + return FileUtil.createFileWithContentsInDirectory("basic_system_info", sb.toString(), //$NON-NLS-1$ + bugReportDirectory.getAbsolutePath()); + } + + private static String humanBytes(long bytes) + { + double v = (double) bytes; + ByteUnit unit = ByteUnit.B; + while (v >= 1024.0 && unit != ByteUnit.PB) + { + v /= 1024.0; + unit = unit.next(); + } + return String.format(Locale.ROOT, "%.2f %s", v, unit.label); //$NON-NLS-1$ + } + + /** + * Generates a bug report zip and also returns the path of the generated zip file. + * + * @return + */ + public String generateBugReport() + { + if (!bugReportDirectory.exists()) + { + bugReportDirectory.mkdirs(); + } + + try + { + File installedToolsFile = getInstalledToolsInfoFile(); + File productInfoFile = getProductInfoFile(); + File basicSysInfoFile = createBasicSystemInfoFile(); + + List filesToZip = new LinkedList<>(); + filesToZip.add(installedToolsFile); + filesToZip.add(productInfoFile); + filesToZip.add(basicSysInfoFile); + + List metadataLogsFile = getIdeMetadataLogsFile(); + File ideLogDir = new File(bugReportDirectory.getAbsolutePath() + File.separator + "ide_metadata_logs"); //$NON-NLS-1$ ) + if (!ideLogDir.exists()) + { + ideLogDir.mkdirs(); + } + + for (File logFile : metadataLogsFile) + { + FileUtil.copyFile(logFile, new File(ideLogDir.getAbsolutePath() + File.separator + logFile.getName())); + } + + File eimLogPath = getEimLogPath(); + Logger.log("EIM log path: " + eimLogPath.getAbsolutePath()); //$NON-NLS-1$ + File eimLogDir = new File(bugReportDirectory.getAbsolutePath() + File.separator + "eim_logs"); //$NON-NLS-1$ ) + IStatus status = FileUtil.copyDirectory(eimLogPath, eimLogDir); + Logger.log("EIM log copy status-code: " + status.getCode()); //$NON-NLS-1$ + Logger.log("EIM log copy status-message: " + status.getMessage()); //$NON-NLS-1$ + + + // Zip the bug report directory + FileUtil.zipDirectory(bugReportDirectory, bugReportDirectory.getAbsolutePath() + ".zip"); //$NON-NLS-1$ + } + catch (IOException e) + { + Logger.log(e); + } + return bugReportDirectory.getAbsolutePath() + ".zip"; //$NON-NLS-1$ + } + + private File getInstalledToolsInfoFile() throws IOException + { + File installedToolsFile = new File(bugReportDirectory, "installed_tools.txt"); //$NON-NLS-1$ + + List arguments = new ArrayList(); + IPath gitPath = new SystemExecutableFinder().find("git"); //$NON-NLS-1$ + Logger.log("GIT path:" + gitPath); //$NON-NLS-1$ + String gitExecutablePath = gitPath.getDevice(); + if (StringUtil.isEmpty(gitExecutablePath)) + { + gitExecutablePath = new IDFEnvironmentVariables().getEnvValue(IDFEnvironmentVariables.GIT_PATH); + } + + arguments.add(IDFUtil.getIDFPythonEnvPath()); + arguments.add(IDFUtil.getIDFToolsScriptFile().getAbsolutePath()); + arguments.add(IDFConstants.TOOLS_LIST_CMD); + Logger.log("Executing command: " + String.join(" ", arguments)); //$NON-NLS-1$ //$NON-NLS-2$ + Map environment = new HashMap<>(IDFUtil.getSystemEnv()); + Logger.log(environment.toString()); + environment.put("PYTHONUNBUFFERED", "1"); //$NON-NLS-1$ //$NON-NLS-2$ + + environment.put("IDF_GITHUB_ASSETS", //$NON-NLS-1$ + Platform.getPreferencesService().getString(IDFCorePlugin.PLUGIN_ID, + IDFCorePreferenceConstants.IDF_GITHUB_ASSETS, + IDFCorePreferenceConstants.IDF_GITHUB_ASSETS_DEFAULT_GLOBAL, null)); + + environment.put("PIP_EXTRA_INDEX_URL", //$NON-NLS-1$ + Platform.getPreferencesService().getString(IDFCorePlugin.PLUGIN_ID, + IDFCorePreferenceConstants.PIP_EXTRA_INDEX_URL, + IDFCorePreferenceConstants.PIP_EXTRA_INDEX_URL_DEFAULT_GLOBAL, null)); + + if (StringUtil.isEmpty(gitExecutablePath)) + { + Logger.log("Git executable path is empty. Please check GIT_PATH environment variable."); //$NON-NLS-1$ + } + else + { + addGitToEnvironment(environment, gitExecutablePath); + } + String output = runCommand(arguments, environment); + Files.write(installedToolsFile.toPath(), output.getBytes(), StandardOpenOption.CREATE_NEW); + return installedToolsFile; + } + + private void addGitToEnvironment(Map environment, String gitExecutablePath) + { + IPath gitPath = new org.eclipse.core.runtime.Path(gitExecutablePath); + if (gitPath.toFile().exists()) + { + String gitDir = gitPath.removeLastSegments(1).toOSString(); + String path1 = environment.get("PATH"); //$NON-NLS-1$ + String path2 = environment.get("Path"); //$NON-NLS-1$ + if (!StringUtil.isEmpty(path1) && !path1.contains(gitDir)) // Git not found on the PATH environment + { + path1 = gitDir.concat(";").concat(path1); //$NON-NLS-1$ + environment.put("PATH", path1); //$NON-NLS-1$ + } + else if (!StringUtil.isEmpty(path2) && !path2.contains(gitDir)) // Git not found on the Path environment + { + path2 = gitDir.concat(";").concat(path2); //$NON-NLS-1$ + environment.put("Path", path2); //$NON-NLS-1$ + } + } + } + + private File getProductInfoFile() throws IOException + { + File productInfoFile = new File(bugReportDirectory, "product_information.txt"); //$NON-NLS-1$ + + String pythonExe = IDFUtil.getIDFPythonEnvPath(); + String idfPath = IDFUtil.getIDFPath(); + if (StringUtil.isEmpty(pythonExe) || StringUtil.isEmpty(idfPath)) + { + Files.write(productInfoFile.toPath(), "IDF_PATH and IDF_PYTHON_ENV_PATH are not found".getBytes(), //$NON-NLS-1$ + StandardOpenOption.CREATE_NEW); + return productInfoFile; + } + + StringBuilder sb = new StringBuilder(); + sb.append(System.lineSeparator()); + sb.append(OPERATING_SYSTEM_MSG + System.getProperty("os.name").toLowerCase()); //$NON-NLS-1$ + sb.append(System.lineSeparator()); + sb.append(JAVA_RUNTIME_VERSION_MSG + + (Optional.ofNullable(System.getProperty("java.runtime.version")).orElse(NOT_FOUND_MSG))); //$NON-NLS-1$ + sb.append(System.lineSeparator()); + sb.append(ECLIPSE_VERSION_MSG + (Optional.ofNullable(Platform.getBundle("org.eclipse.platform")) //$NON-NLS-1$ + .map(o -> o.getVersion().toString()).orElse(NOT_FOUND_MSG))); // $NON-NLS-1$ + sb.append(System.lineSeparator()); + sb.append(ECLIPSE_CDT_MSG + (Optional.ofNullable(Platform.getBundle("org.eclipse.cdt")) //$NON-NLS-1$ + .map(o -> o.getVersion().toString()).orElse(NOT_FOUND_MSG))); // $NON-NLS-1$ + sb.append(System.lineSeparator()); + sb.append( + IDF_ECLIPSE_PLUGIN_VERSION_MSG + (Optional.ofNullable(Platform.getBundle("com.espressif.idf.branding")) //$NON-NLS-1$ + .map(o -> o.getVersion().toString()).orElse(NOT_FOUND_MSG))); // $NON-NLS-1$ + sb.append(System.lineSeparator()); + showEspIdfVersion(); + sb.append(PYTHON_IDF_ENV_MSG + + (Optional.ofNullable(getPythonExeVersion(IDFUtil.getIDFPythonEnvPath())).orElse(NOT_FOUND_MSG))); + sb.append(System.lineSeparator()); + + Files.write(productInfoFile.toPath(), sb.toString().getBytes(), StandardOpenOption.CREATE_NEW); + return productInfoFile; + } + + private void showEspIdfVersion() + { + if (IDFUtil.getIDFPath() != null && IDFUtil.getIDFPythonEnvPath() != null) + { + List commands = new ArrayList<>(); + commands.add(IDFUtil.getIDFPythonEnvPath()); + commands.add(IDFUtil.getIDFPythonScriptFile().getAbsolutePath()); + commands.add("--version"); //$NON-NLS-1$ + Map envMap = new IDFEnvironmentVariables().getSystemEnvMap(); + Logger.log(runCommand(commands, envMap)); + } + else + { + Logger.log("ESP-IDF version cannot be checked. IDF_PATH or IDF_PYTHON_ENV_PATH are not set."); //$NON-NLS-1$ + } + } + + private String runCommand(List arguments, Map env) + { + String exportCmdOp = ""; //$NON-NLS-1$ + ProcessBuilderFactory processRunner = new ProcessBuilderFactory(); + try + { + IStatus status = processRunner.runInBackground(arguments, org.eclipse.core.runtime.Path.ROOT, env); + if (status == null) + { + Logger.log(IDFCorePlugin.getPlugin(), IDFCorePlugin.errorStatus("Status can't be null", null)); //$NON-NLS-1$ + } + + // process export command output + exportCmdOp = status.getMessage(); + Logger.log(exportCmdOp); + } + catch (Exception e1) + { + Logger.log(IDFCorePlugin.getPlugin(), e1); + } + return exportCmdOp; + } + + private String getPythonExeVersion(String pythonExePath) + { + List commands = new ArrayList<>(); + commands.add(pythonExePath); + commands.add("--version"); //$NON-NLS-1$ + return pythonExePath != null ? runCommand(commands, IDFUtil.getSystemEnv()) : null; + } +} \ No newline at end of file diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/bug/GithubIssueOpener.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/bug/GithubIssueOpener.java new file mode 100644 index 000000000..174cc680c --- /dev/null +++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/bug/GithubIssueOpener.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright 2025 Espressif Systems (Shanghai) PTE LTD. + * All rights reserved. Use is subject to license terms. + *******************************************************************************/ +package com.espressif.idf.core.bug; + +import java.awt.Desktop; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * Opens the default web browser to create a new issue on the GitHub repository. + * + * @author Ali Azam Rana + */ +public class GithubIssueOpener +{ + private static final String issueUrlBase = "https://github.com/espressif/idf-eclipse-plugin/issues/new"; //$NON-NLS-1$ + + public static void openNewIssue() + throws Exception + { + String q = "&template=" + enc("bug_report.md"); // e.g. "bug_report.md" //$NON-NLS-1$ //$NON-NLS-2$ + URI uri = new URI(issueUrlBase + "?" + q); //$NON-NLS-1$ + Desktop.getDesktop().browse(uri); + } + + private static String enc(String s) throws UnsupportedEncodingException + { + return URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + } +} diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/FileUtil.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/FileUtil.java index 5e951f4d7..476e89616 100644 --- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/FileUtil.java +++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/util/FileUtil.java @@ -8,14 +8,21 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Paths; import java.text.MessageFormat; +import java.util.List; import java.util.Scanner; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; @@ -348,7 +355,7 @@ public static String readFile(String absoluteFilePath) while (scanner.hasNext()) { fileContents.append(scanner.nextLine()); - fileContents.append(System.getProperty("line.separator")); + fileContents.append(System.getProperty("line.separator")); //$NON-NLS-1$ } scanner.close(); } @@ -405,5 +412,82 @@ public static void deleteDirectory(File file) throws IOException throw new IOException("Failed to delete " + file); //$NON-NLS-1$ } } + + public static File createFileWithContentsInDirectory(String fileName, String content, String direcotry) throws IOException + { + java.nio.file.Path directoryPath = java.nio.file.Paths.get(direcotry); + java.nio.file.Path filePath = directoryPath.resolve(fileName); + try (FileWriter writer = new FileWriter(filePath.toFile())) + { + writer.write(content); + } + return filePath.toFile(); + } + + public static void zipDirectory(File directoryToZip, String zipFileName) throws IOException + { + if (directoryToZip == null || !directoryToZip.isDirectory()) + { + throw new IllegalArgumentException("directoryToZip must be an existing directory"); //$NON-NLS-1$ + } + + java.nio.file.Path base = directoryToZip.toPath(); + java.nio.file.Path zipPath = Paths.get(zipFileName); + String rootName = base.getFileName().toString(); + + List paths; + try (Stream walk = Files.walk(base)) + { + paths = walk.sorted((p1, p2) -> { + boolean d1 = Files.isDirectory(p1); + boolean d2 = Files.isDirectory(p2); + if (d1 != d2) + return d1 ? -1 : 1; // dirs before files + String r1 = base.relativize(p1).toString().replace(File.separatorChar, '/'); + String r2 = base.relativize(p2).toString().replace(File.separatorChar, '/'); + return r1.compareTo(r2); + }).toList(); + } + + if (zipPath.getParent() != null) + { + Files.createDirectories(zipPath.getParent()); + } + + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath))) + { + ZipEntry rootEntry = new ZipEntry(rootName + "/"); //$NON-NLS-1$ + rootEntry.setTime(Files.getLastModifiedTime(base).toMillis()); + zos.putNextEntry(rootEntry); + zos.closeEntry(); + + for (java.nio.file.Path p : paths) + { + if (p.equals(base)) + continue; // root already added + + String rel = base.relativize(p).toString().replace(File.separatorChar, '/'); + String entryName = rootName + "/" + rel; //$NON-NLS-1$ + + if (Files.isDirectory(p)) + { + if (!entryName.endsWith("/")) //$NON-NLS-1$ + entryName += "/"; //$NON-NLS-1$ + ZipEntry dirEntry = new ZipEntry(entryName); + dirEntry.setTime(Files.getLastModifiedTime(p).toMillis()); + zos.putNextEntry(dirEntry); + zos.closeEntry(); + } + else + { + ZipEntry fileEntry = new ZipEntry(entryName); + fileEntry.setTime(Files.getLastModifiedTime(p).toMillis()); + zos.putNextEntry(fileEntry); + Files.copy(p, zos); + zos.closeEntry(); + } + } + } + } } diff --git a/bundles/com.espressif.idf.ui/plugin.xml b/bundles/com.espressif.idf.ui/plugin.xml index e194e00c2..2efd03d66 100644 --- a/bundles/com.espressif.idf.ui/plugin.xml +++ b/bundles/com.espressif.idf.ui/plugin.xml @@ -113,6 +113,17 @@ style="push"> + + + + + + { + result[0] = findConsole(name); + }); + return result[0]; + } + + private MessageConsole findConsole(String name) + { + ConsolePlugin plugin = ConsolePlugin.getDefault(); + IConsoleManager conMan = plugin.getConsoleManager(); + IConsole[] existing = conMan.getConsoles(); + for (IConsole ic : existing) + { + if (name.equals(ic.getName())) + { + return (MessageConsole) ic; + } + } + // No console found, create a new one (no icon) + MessageConsole myConsole = new MessageConsole(name, null); + conMan.addConsoles(new IConsole[] { myConsole }); + return myConsole; + } +} diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java index 68103c68c..e63d769d6 100644 --- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java +++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java @@ -74,6 +74,8 @@ public class Messages extends NLS public static String ToolsInstallationJobCompletedMessage; + public static String BugReportHandler_CompletedBugReportMsg; + static { // initialize resource bundle diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties index a449ac7a9..26b2962ea 100644 --- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties +++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties @@ -65,3 +65,6 @@ SbomCommandDialog_SbomTitle=Software Bill of Materials Tool SbomCommandDialog_StatusCantBeNullErrorMsg=Operation status cannot be null. Please ensure the operation was executed correctly. ToolsInstallationJobCompletedMessage=Tool installation has been successfully completed. To utilize specific tools, please use \"Set Active\" button + + +BugReportHandler_CompletedBugReportMsg=\n\n================================\nBug report completed. You can find the report at: %s \n=================================\n \ No newline at end of file