From d8b4bd8c9f65c05bdf24a41fd8bb710b88b41df8 Mon Sep 17 00:00:00 2001 From: ShodiBoy1 Date: Wed, 2 Sep 2026 20:17:06 +0200 Subject: [PATCH] #871: add progress bar to DMG extraction --- CHANGELOG.adoc | 1 + .../devonfw/tools/ide/io/FileAccessImpl.java | 26 ++++++++++++++- .../tools/ide/io/FileAccessImplTest.java | 33 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c7377d8efd..39646e7f38 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE Release with new features and bugfixes: +* https://github.com/devonfw/IDEasy/issues/871[#871]: Add a progress bar when copying applications from DMG files The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/50?closed=1[milestone 2026.09.002]. diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java index 931c59a5d6..e86a4ed507 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java @@ -213,6 +213,18 @@ private void copyFileWithProgressBar(Path source, Path target) { } } + private void copyTreeWithProgressBar(Path source, Path target, FileCopyMode mode) { + + long size = getPathSize(source); + try (IdeProgressBar progressBar = this.context.newProgressbarForCopying(size)) { + copy(source, target, mode, (copiedSource, copiedTarget, directory) -> { + if (!directory) { + progressBar.stepBy(getFileSize(copiedSource)); + } + }); + } + } + @Override public String download(String url) { @@ -1009,7 +1021,7 @@ public void extractDmg(Path file, Path targetDir) { throw new IllegalStateException("Failed to unpack DMG as no MacOS *.app was found in file " + file); } - copy(appPath, targetDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE); + copyTreeWithProgressBar(appPath, targetDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE); pc.addArgs("detach", "-force", mountPath); pc.run(); } @@ -1425,6 +1437,18 @@ private long getFileSize(Path file) { } } + private long getPathSize(Path path) { + + if (!Files.isDirectory(path)) { + return getFileSize(path); + } + long size = 0; + for (Path child : listChildren(path, file -> true)) { + size += getPathSize(child); + } + return size; + } + @Override public Path findExistingFile(String fileName, List searchDirs) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java index a620211ad4..654f230bb7 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java @@ -24,10 +24,13 @@ import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.context.IdeTestContext; import com.devonfw.tools.ide.os.SystemInfoMock; +import com.devonfw.tools.ide.process.ProcessContext; /** * Test of {@link FileAccessImpl}. @@ -799,6 +802,36 @@ void testUnzipFilePermissionsSkippedOnSimulatedWindows(@TempDir Path tempDir) { } } + /** + * Test of {@link FileAccessImpl#extractDmg(Path, Path)} with a progress bar for copying the mounted app. + */ + @Test + void testExtractDmgWithProgressBar(@TempDir Path tempDir) throws IOException { + + // arrange + IdeTestContext context = newContext(tempDir); + context.setIdeHome(tempDir); + context.setSystemInfo(SystemInfoMock.MAC_X64); + ProcessContext processContext = Mockito.mock(ProcessContext.class); + context.setProcessContext(processContext); + Path appPath = context.getIdeHome().resolve(IdeContext.FOLDER_UPDATES).resolve(IdeContext.FOLDER_VOLUME).resolve("MyApp.app"); + Path sourceFile = appPath.resolve("Contents/Resources/resource.txt"); + Files.createDirectories(sourceFile.getParent()); + Files.writeString(sourceFile, "x".repeat(1024)); + long appSize = Files.size(sourceFile); + Path target = tempDir.resolve("target"); + + // act + context.getFileAccess().extractDmg(tempDir.resolve("MyApp.dmg"), target); + + // assert + assertThat(target.resolve(appPath.getFileName()).resolve(appPath.relativize(sourceFile))).hasSameTextualContentAs(sourceFile); + IdeProgressBarTestImpl progressBar = context.getProgressBarMap().get(IdeProgressBar.TITLE_COPYING); + assertThat(progressBar).isNotNull(); + assertThat(progressBar.getMaxSize()).isEqualTo(appSize); + assertThat(progressBar.getEventList()).extracting(IdeProgressBarTestImpl.ProgressEvent::getStepSize).containsExactly(appSize); + } + /** * Test of {@link FileAccessImpl#generatePermissionString(int)}. */