Skip to content

Commit 1e650d7

Browse files
authored
#347: Change copy method behavior to be consistent (#359)
1 parent e2ab777 commit 1e650d7

File tree

6 files changed

+101
-130
lines changed

6 files changed

+101
-130
lines changed

cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package com.devonfw.tools.ide.commandlet;
22

3-
import java.nio.file.Files;
4-
import java.nio.file.Path;
5-
import java.util.HashSet;
6-
import java.util.List;
7-
import java.util.Set;
8-
93
import com.devonfw.tools.ide.context.GitContext;
104
import com.devonfw.tools.ide.context.IdeContext;
115
import com.devonfw.tools.ide.property.StringProperty;
@@ -15,6 +9,12 @@
159
import com.devonfw.tools.ide.tool.ToolCommandlet;
1610
import com.devonfw.tools.ide.variable.IdeVariables;
1711

12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.util.HashSet;
15+
import java.util.List;
16+
import java.util.Set;
17+
1818
/**
1919
* Abstract {@link Commandlet} base-class for both {@link UpdateCommandlet} and {@link CreateCommandlet}.
2020
*/
@@ -78,8 +78,8 @@ private void setupConf(Path template, Path conf) {
7878
this.context.debug("Configuration {} already exists - skipping to copy from {}", confPath, child);
7979
} else {
8080
if (!basename.equals("settings.xml")) {
81-
this.context.info("Copying template {} to {}.", child, confPath);
82-
this.context.getFileAccess().copy(child, confPath);
81+
this.context.info("Copying template {} to {}.", child, conf);
82+
this.context.getFileAccess().copy(child, conf);
8383
}
8484
}
8585
}
@@ -104,8 +104,8 @@ private void updateSettings() {
104104
String message = "Missing your settings at " + settingsPath + " and no SETTINGS_URL is defined.\n"
105105
+ "Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.asciidoc\n"
106106
+ "Please contact the technical lead of your project to get the SETTINGS_URL for your project.\n"
107-
+ "In case you just want to test IDEasy you may simply hit return to install the default settings.\n"
108-
+ "Settings URL [" + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:";
107+
+ "In case you just want to test IDEasy you may simply hit return to install the default settings.\n" + "Settings URL ["
108+
+ IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:";
109109
repository = this.context.askForInput(message, IdeContext.DEFAULT_SETTINGS_REPO_URL);
110110
} else if ("-".equals(repository)) {
111111
repository = IdeContext.DEFAULT_SETTINGS_REPO_URL;
@@ -127,8 +127,7 @@ private void updateSoftware() {
127127
Set<ToolCommandlet> toolCommandlets = new HashSet<>();
128128

129129
// installed tools in IDE_HOME/software
130-
List<Path> softwares = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(),
131-
Files::isDirectory);
130+
List<Path> softwares = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(), Files::isDirectory);
132131
for (Path software : softwares) {
133132
String toolName = software.getFileName().toString();
134133
ToolCommandlet toolCommandlet = this.context.getCommandletManager().getToolCommandletOrNull(toolName);

cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,18 +282,20 @@ public void move(Path source, Path targetDir) {
282282
@Override
283283
public void copy(Path source, Path target, FileCopyMode mode) {
284284

285+
if (mode != FileCopyMode.COPY_TREE_CONTENT) {
286+
// if we want to copy the file or folder "source" to the existing folder "target" in a shell this will copy
287+
// source into that folder so that we as a result have a copy in "target/source".
288+
// With Java NIO the raw copy method will fail as we cannot copy "source" to the path of the "target" folder.
289+
// For folders we want the same behavior as the linux "cp -r" command so that the "source" folder is copied
290+
// and not only its content what also makes it consistent with the move method that also behaves this way.
291+
// Therefore we need to add the filename (foldername) of "source" to the "target" path before.
292+
// For the rare cases, where we want to copy the content of a folder (cp -r source/* target) we support
293+
// it via the COPY_TREE_CONTENT mode.
294+
target = target.resolve(source.getFileName());
295+
}
285296
boolean fileOnly = mode.isFileOnly();
286297
if (fileOnly) {
287298
this.context.debug("Copying file {} to {}", source, target);
288-
if (Files.isDirectory(target)) {
289-
// if we want to copy "file.txt" to the existing folder "path/to/folder/" in a shell this will copy "file.txt"
290-
// into that folder
291-
// with Java NIO the raw copy method will fail as we cannot copy the file to the path of the target folder
292-
// even worse if FileCopyMode is override the target folder ("path/to/folder/") would be deleted and the result
293-
// of our "file.txt" would later appear in "path/to/folder". To prevent such bugs we append the filename to
294-
// target
295-
target = target.resolve(source.getFileName());
296-
}
297299
} else {
298300
this.context.debug("Copying {} recursively to {}", source, target);
299301
}
@@ -501,12 +503,8 @@ public void extract(Path archiveFile, Path targetDir, Consumer<Path> postExtract
501503

502504
if (Files.isDirectory(archiveFile)) {
503505
Path properInstallDir = archiveFile; // getProperInstallationSubDirOf(archiveFile, archiveFile);
504-
if (extract) {
505-
this.context.warning("Found directory for download at {} hence copying without extraction!", archiveFile);
506-
copy(properInstallDir, targetDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE);
507-
} else {
508-
move(properInstallDir, targetDir);
509-
}
506+
this.context.warning("Found directory for download at {} hence copying without extraction!", archiveFile);
507+
copy(properInstallDir, targetDir, FileCopyMode.COPY_TREE_CONTENT);
510508
postExtractHook(postExtractHook, properInstallDir);
511509
return;
512510
} else if (!extract) {

cli/src/main/java/com/devonfw/tools/ide/io/FileCopyMode.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package com.devonfw.tools.ide.io;
22

33
/**
4-
* {@link Enum} with the available modes to
5-
* {@link FileAccess#copy(java.nio.file.Path, java.nio.file.Path, FileCopyMode) copy} files and folders.
4+
* {@link Enum} with the available modes to {@link FileAccess#copy(java.nio.file.Path, java.nio.file.Path, FileCopyMode) copy} files and folders.
65
*/
76
public enum FileCopyMode {
87

98
/**
10-
* Copy {@link #isFileOnly() only a single file} and
11-
* {@link #isFailIfExists() fail if the target-file already exists}.
9+
* Copy {@link #isFileOnly() only a single file} and {@link #isFailIfExists() fail if the target-file already exists}.
1210
*/
1311
COPY_FILE_FAIL_IF_EXISTS,
1412

@@ -22,23 +20,26 @@ public enum FileCopyMode {
2220
COPY_TREE_OVERRIDE_FILES,
2321

2422
/**
25-
* Copy {@link #isRecursive() recursively} and {@link FileAccess#delete(java.nio.file.Path) delete} the target-file if
26-
* it exists before copying.
23+
* Copy {@link #isRecursive() recursively} and {@link FileAccess#delete(java.nio.file.Path) delete} the target-file if it exists before copying.
2724
*/
28-
COPY_TREE_OVERRIDE_TREE;
25+
COPY_TREE_OVERRIDE_TREE,
2926

3027
/**
31-
* @return {@code true} if only a single file shall be copied. Will fail if a directory is given to copy,
32-
* {@code false} otherwise (to copy folders recursively).
28+
* Copy {@link #isRecursive() recursively} and appends the file name to the target.
29+
*/
30+
COPY_TREE_CONTENT;
31+
32+
/**
33+
* @return {@code true} if only a single file shall be copied. Will fail if a directory is given to copy, {@code false} otherwise (to copy folders
34+
* recursively).
3335
*/
3436
public boolean isFileOnly() {
3537

3638
return (this == COPY_FILE_FAIL_IF_EXISTS) || (this == COPY_FILE_OVERRIDE);
3739
}
3840

3941
/**
40-
* @return {@code true} if files and folders shall be copied recursively, {@code false} otherwise
41-
* ({@link #isFileOnly() copy file copy}).
42+
* @return {@code true} if files and folders shall be copied recursively, {@code false} otherwise ({@link #isFileOnly() copy file copy}).
4243
*/
4344
public boolean isRecursive() {
4445

cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package com.devonfw.tools.ide.tool;
22

3-
import java.io.IOException;
4-
import java.nio.file.Files;
5-
import java.nio.file.Path;
6-
import java.nio.file.StandardOpenOption;
7-
import java.util.Set;
8-
93
import com.devonfw.tools.ide.common.Tag;
104
import com.devonfw.tools.ide.context.IdeContext;
115
import com.devonfw.tools.ide.io.FileAccess;
@@ -15,6 +9,12 @@
159
import com.devonfw.tools.ide.step.Step;
1610
import com.devonfw.tools.ide.version.VersionIdentifier;
1711

12+
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.StandardOpenOption;
16+
import java.util.Set;
17+
1818
/**
1919
* {@link ToolCommandlet} that is installed locally into the IDE.
2020
*/
@@ -25,8 +25,7 @@ public abstract class LocalToolCommandlet extends ToolCommandlet {
2525
*
2626
* @param context the {@link IdeContext}.
2727
* @param tool the {@link #getName() tool name}.
28-
* @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of}
29-
* method.
28+
* @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
3029
*/
3130
public LocalToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {
3231

@@ -42,14 +41,12 @@ public Path getToolPath() {
4241
}
4342

4443
/**
45-
* @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside
46-
* {@link #getToolPath() tool path}.
44+
* @return the {@link Path} where the executables of the tool can be found. Typically a "bin" folder inside {@link #getToolPath() tool path}.
4745
*/
4846
public Path getToolBinPath() {
4947

5048
Path toolPath = getToolPath();
51-
Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"),
52-
false);
49+
Path binPath = this.context.getFileAccess().findFirst(toolPath, path -> path.getFileName().toString().equals("bin"), false);
5350
if ((binPath != null) && Files.isDirectory(binPath)) {
5451
return binPath;
5552
}
@@ -70,8 +67,7 @@ protected boolean doInstall(boolean silent) {
7067
VersionIdentifier resolvedVersion = installation.resolvedVersion();
7168
if (resolvedVersion.equals(installedVersion) && !installation.newInstallation()) {
7269
IdeLogLevel level = silent ? IdeLogLevel.DEBUG : IdeLogLevel.INFO;
73-
this.context.level(level).log("Version {} of tool {} is already installed", installedVersion,
74-
getToolWithEdition());
70+
this.context.level(level).log("Version {} of tool {} is already installed", installedVersion, getToolWithEdition());
7571
step.success();
7672
return false;
7773
}
@@ -88,8 +84,7 @@ protected boolean doInstall(boolean silent) {
8884
if (installedVersion == null) {
8985
step.success("Successfully installed {} in version {}", this.tool, resolvedVersion);
9086
} else {
91-
step.success("Successfully installed {} in version {} replacing previous version {}", this.tool,
92-
resolvedVersion, installedVersion);
87+
step.success("Successfully installed {} in version {} replacing previous version {}", this.tool, resolvedVersion, installedVersion);
9388
}
9489
return true;
9590
} catch (RuntimeException e) {
@@ -102,12 +97,10 @@ protected boolean doInstall(boolean silent) {
10297
}
10398

10499
/**
105-
* Performs the installation of the {@link #getName() tool} managed by this
106-
* {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software repository without touching the
107-
* IDE installation.
100+
* Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
101+
* repository without touching the IDE installation.
108102
*
109-
* @param version the {@link VersionIdentifier} requested to be installed. May also be a
110-
* {@link VersionIdentifier#isPattern() version pattern}.
103+
* @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
111104
* @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
112105
*/
113106
public ToolInstallation installInRepo(VersionIdentifier version) {
@@ -116,12 +109,10 @@ public ToolInstallation installInRepo(VersionIdentifier version) {
116109
}
117110

118111
/**
119-
* Performs the installation of the {@link #getName() tool} managed by this
120-
* {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software repository without touching the
121-
* IDE installation.
112+
* Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
113+
* repository without touching the IDE installation.
122114
*
123-
* @param version the {@link VersionIdentifier} requested to be installed. May also be a
124-
* {@link VersionIdentifier#isPattern() version pattern}.
115+
* @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
125116
* @param edition the specific edition to install.
126117
* @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
127118
*/
@@ -131,30 +122,27 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition)
131122
}
132123

133124
/**
134-
* Performs the installation of the {@link #getName() tool} managed by this
135-
* {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software repository without touching the
136-
* IDE installation.
125+
* Performs the installation of the {@link #getName() tool} managed by this {@link com.devonfw.tools.ide.commandlet.Commandlet} only in the central software
126+
* repository without touching the IDE installation.
137127
*
138-
* @param version the {@link VersionIdentifier} requested to be installed. May also be a
139-
* {@link VersionIdentifier#isPattern() version pattern}.
128+
* @param version the {@link VersionIdentifier} requested to be installed. May also be a {@link VersionIdentifier#isPattern() version pattern}.
140129
* @param edition the specific edition to install.
141130
* @param toolRepository the {@link ToolRepository} to use.
142131
* @return the {@link ToolInstallation} in the central software repository matching the given {@code version}.
143132
*/
144133
public ToolInstallation installInRepo(VersionIdentifier version, String edition, ToolRepository toolRepository) {
145134

146135
VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, version);
147-
Path toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool)
148-
.resolve(edition).resolve(resolvedVersion.toString());
136+
Path toolPath = this.context.getSoftwareRepositoryPath().resolve(toolRepository.getId()).resolve(this.tool).resolve(edition)
137+
.resolve(resolvedVersion.toString());
149138
Path toolVersionFile = toolPath.resolve(IdeContext.FILE_SOFTWARE_VERSION);
150139
FileAccess fileAccess = this.context.getFileAccess();
151140
if (Files.isDirectory(toolPath)) {
152141
if (Files.exists(toolVersionFile)) {
153142
if (this.context.isForceMode()) {
154143
fileAccess.delete(toolPath);
155144
} else {
156-
this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion,
157-
getToolWithEdition(this.tool, edition), toolPath);
145+
this.context.debug("Version {} of tool {} is already installed at {}", resolvedVersion, getToolWithEdition(this.tool, edition), toolPath);
158146
return createToolInstallation(toolPath, resolvedVersion, toolVersionFile);
159147
}
160148
} else {
@@ -166,8 +154,7 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition,
166154
fileAccess.mkdirs(toolPath.getParent());
167155
boolean extract = isExtract();
168156
if (!extract) {
169-
this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool,
170-
target);
157+
this.context.trace("Extraction is disabled for '{}' hence just moving the downloaded file {}.", this.tool, target);
171158
}
172159
fileAccess.extract(target, toolPath, this::postExtract, extract);
173160
try {
@@ -181,17 +168,15 @@ public ToolInstallation installInRepo(VersionIdentifier version, String edition,
181168
}
182169

183170
/**
184-
* Post-extraction hook that can be overridden to add custom processing after unpacking and before moving to the final
185-
* destination folder.
171+
* Post-extraction hook that can be overridden to add custom processing after unpacking and before moving to the final destination folder.
186172
*
187173
* @param extractedDir the {@link Path} to the folder with the unpacked tool.
188174
*/
189175
protected void postExtract(Path extractedDir) {
190176

191177
}
192178

193-
private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile,
194-
boolean newInstallation) {
179+
private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile, boolean newInstallation) {
195180

196181
Path linkDir = getMacOsHelper().findLinkDir(rootDir, this.tool);
197182
Path binDir = linkDir;
@@ -201,14 +186,12 @@ private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier
201186
}
202187
if (linkDir != rootDir) {
203188
assert (!linkDir.equals(rootDir));
204-
this.context.getFileAccess().copy(toolVersionFile, linkDir.resolve(IdeContext.FILE_SOFTWARE_VERSION),
205-
FileCopyMode.COPY_FILE_OVERRIDE);
189+
this.context.getFileAccess().copy(toolVersionFile, linkDir, FileCopyMode.COPY_FILE_OVERRIDE);
206190
}
207191
return new ToolInstallation(rootDir, linkDir, binDir, resolvedVersion, newInstallation);
208192
}
209193

210-
private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion,
211-
Path toolVersionFile) {
194+
private ToolInstallation createToolInstallation(Path rootDir, VersionIdentifier resolvedVersion, Path toolVersionFile) {
212195

213196
return createToolInstallation(rootDir, resolvedVersion, toolVersionFile, false);
214197
}

cli/src/main/java/com/devonfw/tools/ide/tool/npm/Npm.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package com.devonfw.tools.ide.tool.npm;
22

3-
import java.nio.file.Path;
4-
import java.util.Set;
5-
63
import com.devonfw.tools.ide.common.Tag;
74
import com.devonfw.tools.ide.context.IdeContext;
85
import com.devonfw.tools.ide.io.FileAccess;
96
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
107
import com.devonfw.tools.ide.tool.ToolCommandlet;
118
import com.devonfw.tools.ide.tool.node.Node;
129

10+
import java.nio.file.Path;
11+
import java.util.Set;
12+
1313
/**
1414
* {@link ToolCommandlet} for <a href="https://www.npmjs.com/">npm</a>.
1515
*/
@@ -47,10 +47,10 @@ protected void postExtract(Path extractedDir) {
4747
fileAccess.delete(nodeHomePath.resolve(npx));
4848
fileAccess.delete(nodeHomePath.resolve(npx + cmd));
4949

50-
fileAccess.copy(npmBinBath.resolve(npm), nodeHomePath.resolve(npm));
51-
fileAccess.copy(npmBinBath.resolve(npm + cmd), nodeHomePath.resolve(npm + cmd));
52-
fileAccess.copy(npmBinBath.resolve(npx), nodeHomePath.resolve(npx));
53-
fileAccess.copy(npmBinBath.resolve(npx + cmd), nodeHomePath.resolve(npx + cmd));
50+
fileAccess.copy(npmBinBath.resolve(npm), nodeHomePath);
51+
fileAccess.copy(npmBinBath.resolve(npm + cmd), nodeHomePath);
52+
fileAccess.copy(npmBinBath.resolve(npx), nodeHomePath);
53+
fileAccess.copy(npmBinBath.resolve(npx + cmd), nodeHomePath);
5454
}
5555
}
5656
}

0 commit comments

Comments
 (0)