This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new fa54e5707b Fix moveTo not working in all situations, fixes #5936 
(#7918)
fa54e5707b is described below

commit fa54e5707b282299929451bb0135ee3cc17b61f3
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Aug 13 08:40:04 2026 +0200

    Fix moveTo not working in all situations, fixes #5936 (#7918)
---
 .../hop/core/config/ConfigFileSerializer.java      |   2 +-
 .../main/java/org/apache/hop/core/vfs/HopVfs.java  |  33 +++
 .../apache/hop/core/vfs/HopVfsMoveFileTest.java    | 142 ++++++++++
 .../hop/junit/vfs/CrossDeviceFileProvider.java     |  76 +++++
 .../apache/hop/execution/caching/CacheEntry.java   |   2 +-
 .../workflow/actions/checkfilelocked/LockFile.java |   2 +
 .../ActionCopyMoveResultFilenames.java             |   2 +-
 .../actions/movefiles/ActionMoveFiles.java         |  12 +-
 .../pgpdecryptfiles/ActionPGPDecryptFiles.java     |   6 +-
 .../pgpencryptfiles/ActionPGPEncryptFiles.java     |   6 +-
 .../hop/workflow/actions/unzip/ActionUnZip.java    |   2 +-
 .../workflow/actions/zipfile/ActionZipFile.java    |   2 +-
 .../hop/pipeline/transforms/sftpput/SftpPut.java   |  34 +--
 .../workflow/actions/sftpput/ActionSftpPut.java    |   2 +-
 .../actions/sftpput/ActionSftpPutMoveTest.java     | 308 +++++++++++++++++++++
 .../javascript/ScriptValuesAddedFunctions.java     |   2 +-
 .../transforms/processfiles/ProcessFiles.java      |   2 +-
 .../hop/pipeline/transforms/zipfile/ZipFile.java   |   2 +-
 .../apache/hop/ui/core/vfs/HopVfsFileDialog.java   |   2 +-
 .../perspective/explorer/ExplorerPerspective.java  |   8 +-
 20 files changed, 599 insertions(+), 48 deletions(-)

diff --git 
a/core/src/main/java/org/apache/hop/core/config/ConfigFileSerializer.java 
b/core/src/main/java/org/apache/hop/core/config/ConfigFileSerializer.java
index 08598ced73..4c6d9c8cc7 100644
--- a/core/src/main/java/org/apache/hop/core/config/ConfigFileSerializer.java
+++ b/core/src/main/java/org/apache/hop/core/config/ConfigFileSerializer.java
@@ -73,7 +73,7 @@ public class ConfigFileSerializer implements 
IHopConfigSerializer {
 
       // Now rename the new file to the final value...
       //
-      newFile.moveTo(file);
+      HopVfs.moveFile(newFile, file);
     } catch (Exception e) {
       throw new HopException("Error writing to Hop configuration file : " + 
filename, e);
     }
diff --git a/core/src/main/java/org/apache/hop/core/vfs/HopVfs.java 
b/core/src/main/java/org/apache/hop/core/vfs/HopVfs.java
index 205ca8e0be..6eb3f7d575 100644
--- a/core/src/main/java/org/apache/hop/core/vfs/HopVfs.java
+++ b/core/src/main/java/org/apache/hop/core/vfs/HopVfs.java
@@ -35,6 +35,7 @@ import org.apache.commons.vfs2.FileContent;
 import org.apache.commons.vfs2.FileName;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.Selectors;
 import org.apache.commons.vfs2.cache.SoftRefFilesCache;
 import org.apache.commons.vfs2.impl.DefaultFileReplicator;
 import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
@@ -510,6 +511,38 @@ public class HopVfs {
     }
   }
 
+  /**
+   * Move a file or a folder, also when the source and the destination sit on 
two different file
+   * systems of the operating system.
+   *
+   * <p>{@link FileObject#moveTo(FileObject)} only copies and deletes when the 
two files belong to
+   * two different VFS file systems. Two local folders are one and the same 
VFS file system
+   * whichever disks they're on, so it always picks a rename, and a rename 
across a mount point is
+   * exactly what the operating system refuses. Everything in Hop that moves a 
file to a folder the
+   * user picked has to go through here, or it breaks the moment those two 
folders are two different
+   * mounts. See <a href="https://github.com/apache/hop/issues/5936";>issue 
#5936</a>.
+   *
+   * @param source the file or folder to move
+   * @param destination where to move it to
+   * @throws FileSystemException when the move failed. When the copy which 
follows a failed rename
+   *     fails as well, this is the original rename failure with the copy 
failure suppressed.
+   */
+  public static void moveFile(FileObject source, FileObject destination)
+      throws FileSystemException {
+    try {
+      source.moveTo(destination);
+    } catch (FileSystemException renameFailed) {
+      try {
+        // SELECT_ALL rather than SELECT_SELF: a folder has to arrive with its 
children.
+        destination.copyFrom(source, Selectors.SELECT_ALL);
+        source.deleteAll();
+      } catch (FileSystemException copyFailed) {
+        renameFailed.addSuppressed(copyFailed);
+        throw renameFailed;
+      }
+    }
+  }
+
   /**
    * Utility to normalize file name depending on OS.
    *
diff --git a/core/src/test/java/org/apache/hop/core/vfs/HopVfsMoveFileTest.java 
b/core/src/test/java/org/apache/hop/core/vfs/HopVfsMoveFileTest.java
new file mode 100644
index 0000000000..4734aa2005
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/core/vfs/HopVfsMoveFileTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.vfs;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.hop.junit.vfs.CrossDeviceFileProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * {@link HopVfs#moveFile(FileObject, FileObject)} is what everything in Hop 
which moves a file has
+ * to use, because {@link FileObject#moveTo(FileObject)} on its own can't move 
a file out of one
+ * mount into another - see <a 
href="https://github.com/apache/hop/issues/5936";>issue #5936</a>.
+ *
+ * <p>{@link CrossDeviceFileProvider} stands in for those two mounts: one VFS 
file system, on which
+ * a rename never succeeds.
+ */
+class HopVfsMoveFileTest {
+
+  private static final String CROSS_DEVICE = "xdev";
+  private static final String PAYLOAD = "id;amount\n1;42\n";
+
+  @TempDir private Path tempDir;
+
+  @BeforeEach
+  void setUp() throws Exception {
+    HopVfs.getFileSystemManager().addProvider(CROSS_DEVICE, new 
CrossDeviceFileProvider());
+  }
+
+  @AfterEach
+  void tearDown() {
+    // The provider above is registered on the one and only file system 
manager.
+    HopVfs.reset();
+  }
+
+  @Test
+  @DisplayName("a file lands in the destination folder when the rename fails")
+  void movesAFileWhenTheRenameFails() throws Exception {
+    FileObject source = write("xdev:///work/sales.csv", PAYLOAD);
+    FileObject destination = HopVfs.getFileObject("xdev:///archive/sales.csv");
+
+    HopVfs.moveFile(source, destination);
+
+    assertTrue(destination.exists(), "the file was not moved");
+    assertFalse(source.exists(), "the file is still in the source folder");
+    assertEquals(PAYLOAD, read(destination));
+  }
+
+  /** What the file system manager hands out for the destination folder is the 
folder itself. */
+  @Test
+  @DisplayName("a folder arrives with its children when the rename fails")
+  void movesAFolderWithItsChildren() throws Exception {
+    write("xdev:///work/orders/january.csv", PAYLOAD);
+    write("xdev:///work/orders/details/lines.csv", PAYLOAD);
+    FileObject source = HopVfs.getFileObject("xdev:///work/orders");
+    FileObject destination = HopVfs.getFileObject("xdev:///archive/orders");
+
+    HopVfs.moveFile(source, destination);
+
+    assertFalse(source.exists(), "the folder is still in the source folder");
+    assertEquals(PAYLOAD, 
read(HopVfs.getFileObject("xdev:///archive/orders/january.csv")));
+    assertEquals(PAYLOAD, 
read(HopVfs.getFileObject("xdev:///archive/orders/details/lines.csv")));
+  }
+
+  /** A rename which does work is left alone: no copy, no second file. */
+  @Test
+  @DisplayName("a file is renamed when the file system can rename")
+  void renamesAFileWhenTheFileSystemCan() throws Exception {
+    Path work = Files.createDirectory(tempDir.resolve("work"));
+    Path archive = Files.createDirectory(tempDir.resolve("archive"));
+    Files.writeString(work.resolve("sales.csv"), PAYLOAD);
+
+    HopVfs.moveFile(
+        HopVfs.getFileObject(work.resolve("sales.csv").toString()),
+        HopVfs.getFileObject(archive.resolve("sales.csv").toString()));
+
+    assertTrue(Files.exists(archive.resolve("sales.csv")), "the file was not 
moved");
+    assertFalse(Files.exists(work.resolve("sales.csv")), "the file is still in 
the source folder");
+    assertArrayEquals(
+        PAYLOAD.getBytes(UTF_8), 
Files.readAllBytes(archive.resolve("sales.csv")), "content");
+  }
+
+  /**
+   * When the copy can't save the move either, the caller gets the failure of 
the move it asked for,
+   * with the failure of the copy alongside it.
+   */
+  @Test
+  @DisplayName("the rename failure is what's reported when the copy fails too")
+  void reportsTheRenameFailureWhenTheCopyFailsToo() throws Exception {
+    FileObject source = HopVfs.getFileObject("xdev:///work/gone.csv");
+    FileObject destination = HopVfs.getFileObject("xdev:///archive/gone.csv");
+
+    FileSystemException e =
+        assertThrows(FileSystemException.class, () -> HopVfs.moveFile(source, 
destination));
+
+    assertTrue(e.getMessage().contains("rename"), e.getMessage());
+    assertEquals(1, e.getSuppressed().length, "the copy failure should come 
along");
+  }
+
+  private static FileObject write(String uri, String content) throws Exception 
{
+    FileObject fileObject = HopVfs.getFileObject(uri);
+    try (OutputStream outputStream = 
fileObject.getContent().getOutputStream()) {
+      outputStream.write(content.getBytes(UTF_8));
+    }
+    return fileObject;
+  }
+
+  private static String read(FileObject fileObject) throws Exception {
+    try (InputStream inputStream = fileObject.getContent().getInputStream()) {
+      return new String(inputStream.readAllBytes(), UTF_8);
+    }
+  }
+}
diff --git 
a/core/src/test/java/org/apache/hop/junit/vfs/CrossDeviceFileProvider.java 
b/core/src/test/java/org/apache/hop/junit/vfs/CrossDeviceFileProvider.java
new file mode 100644
index 0000000000..f39ea83826
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/junit/vfs/CrossDeviceFileProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.junit.vfs;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystem;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.ram.RamFileObject;
+import org.apache.commons.vfs2.provider.ram.RamFileProvider;
+import org.apache.commons.vfs2.provider.ram.RamFileSystem;
+
+/**
+ * An in-memory file system on which a rename never succeeds, which is what 
the operating system
+ * does with a rename across a mount point. Everything else - reading, 
writing, listing, deleting -
+ * behaves as usual, so the only thing which can go wrong is the move itself.
+ *
+ * <p>Two folders on this file system stand in for two local folders on two 
different mounts: one
+ * VFS file system, so {@link FileObject#moveTo(FileObject)} picks a rename, 
and a rename which
+ * fails. Register it on the file system manager under a scheme of your own 
and resolve through it:
+ *
+ * <pre>
+ *   HopVfs.getFileSystemManager().addProvider("xdev", new 
CrossDeviceFileProvider());
+ *   HopVfs.getFileObject("xdev:///work/sales.csv");
+ * </pre>
+ *
+ * Registering is for the one and only file system manager, so call {@code 
HopVfs.reset()} when the
+ * test is done. See <a href="https://github.com/apache/hop/issues/5936";>issue 
#5936</a>.
+ */
+public class CrossDeviceFileProvider extends RamFileProvider {
+
+  @Override
+  protected FileSystem doCreateFileSystem(FileName rootName, FileSystemOptions 
fileSystemOptions) {
+    return new CrossDeviceFileSystem(rootName, fileSystemOptions);
+  }
+
+  private static class CrossDeviceFileSystem extends RamFileSystem {
+    CrossDeviceFileSystem(FileName rootName, FileSystemOptions 
fileSystemOptions) {
+      super(rootName, fileSystemOptions);
+    }
+
+    @Override
+    protected FileObject createFile(AbstractFileName name) {
+      return new CrossDeviceFileObject(name, this);
+    }
+  }
+
+  private static class CrossDeviceFileObject extends RamFileObject {
+    CrossDeviceFileObject(AbstractFileName name, RamFileSystem fileSystem) {
+      super(name, fileSystem);
+    }
+
+    @Override
+    protected void doRename(FileObject newFile) throws FileSystemException {
+      // Errno EXDEV, which LocalFile reports the same way: File.renameTo() 
returns false.
+      throw new FileSystemException(
+          "vfs.provider.local/rename-file.error", getName(), 
newFile.getName());
+    }
+  }
+}
diff --git 
a/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java 
b/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
index 43b792e30b..68bb027de8 100644
--- a/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
+++ b/engine/src/main/java/org/apache/hop/execution/caching/CacheEntry.java
@@ -129,7 +129,7 @@ public class CacheEntry {
         targetFileObject.delete();
       }
       FileObject fileObject = HopVfs.getFileObject(filename, variables);
-      fileObject.moveTo(targetFileObject);
+      HopVfs.moveFile(fileObject, targetFileObject);
     } catch (Exception e) {
       throw new HopException(
           "Error renaming execution information to file '" + targetFilename + 
"'", e);
diff --git 
a/plugins/actions/checkfilelocked/src/main/java/org/apache/hop/workflow/actions/checkfilelocked/LockFile.java
 
b/plugins/actions/checkfilelocked/src/main/java/org/apache/hop/workflow/actions/checkfilelocked/LockFile.java
index a6e897f2b6..1752041715 100644
--- 
a/plugins/actions/checkfilelocked/src/main/java/org/apache/hop/workflow/actions/checkfilelocked/LockFile.java
+++ 
b/plugins/actions/checkfilelocked/src/main/java/org/apache/hop/workflow/actions/checkfilelocked/LockFile.java
@@ -52,6 +52,8 @@ public class LockFile {
       if (file.exists()) {
         dummyfile = HopVfs.getFileObject(filename, variables);
         // move file to itself!
+        // Not HopVfs.moveFile(): here the failing rename is the answer we 
came for, and its
+        // fallback would copy this file onto itself and then delete it.
         file.moveTo(dummyfile);
       }
     } catch (Exception e) {
diff --git 
a/plugins/actions/copymoveresultfilenames/src/main/java/org/apache/hop/workflow/actions/copymoveresultfilenames/ActionCopyMoveResultFilenames.java
 
b/plugins/actions/copymoveresultfilenames/src/main/java/org/apache/hop/workflow/actions/copymoveresultfilenames/ActionCopyMoveResultFilenames.java
index d425336ca7..4db726d90f 100644
--- 
a/plugins/actions/copymoveresultfilenames/src/main/java/org/apache/hop/workflow/actions/copymoveresultfilenames/ActionCopyMoveResultFilenames.java
+++ 
b/plugins/actions/copymoveresultfilenames/src/main/java/org/apache/hop/workflow/actions/copymoveresultfilenames/ActionCopyMoveResultFilenames.java
@@ -410,7 +410,7 @@ public class ActionCopyMoveResultFilenames extends 
ActionBase implements Cloneab
             }
           } else {
             // Move file
-            sourceFile.moveTo(destinationFile);
+            HopVfs.moveFile(sourceFile, destinationFile);
             nrSuccess++;
             if (isDetailed()) {
               logDetailed(
diff --git 
a/plugins/actions/movefiles/src/main/java/org/apache/hop/workflow/actions/movefiles/ActionMoveFiles.java
 
b/plugins/actions/movefiles/src/main/java/org/apache/hop/workflow/actions/movefiles/ActionMoveFiles.java
index fe12175f68..1182d6df37 100644
--- 
a/plugins/actions/movefiles/src/main/java/org/apache/hop/workflow/actions/movefiles/ActionMoveFiles.java
+++ 
b/plugins/actions/movefiles/src/main/java/org/apache/hop/workflow/actions/movefiles/ActionMoveFiles.java
@@ -716,7 +716,7 @@ public class ActionMoveFiles extends ActionBase implements 
Cloneable, IAction {
         if (!simulate) {
           Long moved = trackBytesMoved(sourceFileFolder, result);
           destinationFilename.createFile();
-          sourceFileFolder.moveTo(destinationFilename);
+          HopVfs.moveFile(sourceFileFolder, destinationFilename);
           emitMoveLineage(parentWorkflow, sourceFileFolder, 
destinationFilename, moved);
         }
 
@@ -748,7 +748,7 @@ public class ActionMoveFiles extends ActionBase implements 
Cloneable, IAction {
           case "overwrite_file" -> {
             if (!simulate) {
               Long moved = trackBytesMoved(sourceFileFolder, result);
-              sourceFileFolder.moveTo(destinationFilename);
+              HopVfs.moveFile(sourceFileFolder, destinationFilename);
               emitMoveLineage(parentWorkflow, sourceFileFolder, 
destinationFilename, moved);
             }
             if (isDetailed()) {
@@ -794,7 +794,7 @@ public class ActionMoveFiles extends ActionBase implements 
Cloneable, IAction {
 
             if (!simulate) {
               Long moved = trackBytesMoved(sourceFileFolder, result);
-              sourceFileFolder.moveTo(destinationFile);
+              HopVfs.moveFile(sourceFileFolder, destinationFile);
               emitMoveLineage(parentWorkflow, sourceFileFolder, 
destinationFile, moved);
             }
             if (isDetailed()) {
@@ -853,7 +853,7 @@ public class ActionMoveFiles extends ActionBase implements 
Cloneable, IAction {
             if (!destinationFile.exists()) {
               if (!simulate) {
                 Long moved = trackBytesMoved(sourceFileFolder, result);
-                sourceFileFolder.moveTo(destinationFile);
+                HopVfs.moveFile(sourceFileFolder, destinationFile);
                 emitMoveLineage(parentWorkflow, sourceFileFolder, 
destinationFile, moved);
               }
               if (isDetailed()) {
@@ -875,7 +875,7 @@ public class ActionMoveFiles extends ActionBase implements 
Cloneable, IAction {
                 case "overwrite_file" -> {
                   if (!simulate) {
                     Long moved = trackBytesMoved(sourceFileFolder, result);
-                    sourceFileFolder.moveTo(destinationFile);
+                    HopVfs.moveFile(sourceFileFolder, destinationFile);
                     emitMoveLineage(parentWorkflow, sourceFileFolder, 
destinationFile, moved);
                   }
                   if (isDetailed()) {
@@ -909,7 +909,7 @@ public class ActionMoveFiles extends ActionBase implements 
Cloneable, IAction {
 
                   if (!simulate) {
                     Long moved = trackBytesMoved(sourceFileFolder, result);
-                    sourceFileFolder.moveTo(destinationFile);
+                    HopVfs.moveFile(sourceFileFolder, destinationFile);
                     emitMoveLineage(parentWorkflow, sourceFileFolder, 
destinationFile, moved);
                   }
                   if (isDetailed()) {
diff --git 
a/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpdecryptfiles/ActionPGPDecryptFiles.java
 
b/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpdecryptfiles/ActionPGPDecryptFiles.java
index 084472e471..de6254ca78 100644
--- 
a/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpdecryptfiles/ActionPGPDecryptFiles.java
+++ 
b/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpdecryptfiles/ActionPGPDecryptFiles.java
@@ -805,7 +805,7 @@ public class ActionPGPDecryptFiles extends ActionBase 
implements Cloneable, IAct
                 moveToFolderFolder.toString() + Const.FILE_SEPARATOR + 
shortFilename;
             destinationFile = HopVfs.getFileObject(moveToFileNameFull, 
getVariables());
             if (!destinationFile.exists()) {
-              sourceFileName.moveTo(destinationFile);
+              HopVfs.moveFile(sourceFileName, destinationFile);
               if (isDetailed()) {
                 logDetailed(
                     BaseMessages.getString(
@@ -825,7 +825,7 @@ public class ActionPGPDecryptFiles extends ActionBase 
implements Cloneable, IAct
             } else {
               switch (ifMovedFileExists) {
                 case CONST_OVERWRITE_FILE -> {
-                  sourceFileName.moveTo(destinationFile);
+                  HopVfs.moveFile(sourceFileName, destinationFile);
                   if (isDetailed()) {
                     logDetailed(
                         BaseMessages.getString(
@@ -854,7 +854,7 @@ public class ActionPGPDecryptFiles extends ActionBase 
implements Cloneable, IAct
                       moveToFolderFolder + Const.FILE_SEPARATOR + 
shortFilename;
                   destinationFile = 
HopVfs.getFileObject(destinationFileNameFull, getVariables());
 
-                  sourceFileName.moveTo(destinationFile);
+                  HopVfs.moveFile(sourceFileName, destinationFile);
                   if (isDetailed()) {
                     logDetailed(
                         BaseMessages.getString(
diff --git 
a/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpencryptfiles/ActionPGPEncryptFiles.java
 
b/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpencryptfiles/ActionPGPEncryptFiles.java
index 294ae8e5f5..3fa261c892 100644
--- 
a/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpencryptfiles/ActionPGPEncryptFiles.java
+++ 
b/plugins/actions/pgpfiles/src/main/java/org/apache/hop/workflow/actions/pgpencryptfiles/ActionPGPEncryptFiles.java
@@ -854,7 +854,7 @@ public class ActionPGPEncryptFiles extends ActionBase 
implements Cloneable, IAct
                 moveToFolderFolder.toString() + Const.FILE_SEPARATOR + 
shortFilename;
             destinationFile = HopVfs.getFileObject(moveToFileNameFull, 
getVariables());
             if (!destinationFile.exists()) {
-              sourceFileName.moveTo(destinationFile);
+              HopVfs.moveFile(sourceFileName, destinationFile);
               if (isDetailed()) {
                 logDetailed(
                     BaseMessages.getString(
@@ -874,7 +874,7 @@ public class ActionPGPEncryptFiles extends ActionBase 
implements Cloneable, IAct
             } else {
               switch (ifMovedFileExists) {
                 case "overwrite_file" -> {
-                  sourceFileName.moveTo(destinationFile);
+                  HopVfs.moveFile(sourceFileName, destinationFile);
                   if (isDetailed()) {
                     logDetailed(
                         BaseMessages.getString(
@@ -903,7 +903,7 @@ public class ActionPGPEncryptFiles extends ActionBase 
implements Cloneable, IAct
                       moveToFolderFolder.toString() + Const.FILE_SEPARATOR + 
shortFilename;
                   destinationFile = 
HopVfs.getFileObject(destinationfilenamefull, getVariables());
 
-                  sourceFileName.moveTo(destinationFile);
+                  HopVfs.moveFile(sourceFileName, destinationFile);
                   if (isDetailed()) {
                     logDetailed(
                         BaseMessages.getString(
diff --git 
a/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
 
b/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
index 921357d62d..3bef8898a2 100644
--- 
a/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
+++ 
b/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
@@ -907,7 +907,7 @@ public class ActionUnZip extends ActionBase implements 
Cloneable, IAction {
             true,
             null);
 
-        sourceFileObject.moveTo(destFile);
+        HopVfs.moveFile(sourceFileObject, destFile);
 
         if (archiveSize > 0) {
           result.setBytesWrittenThisAction(result.getBytesWrittenThisAction() 
+ archiveSize);
diff --git 
a/plugins/actions/zipfile/src/main/java/org/apache/hop/workflow/actions/zipfile/ActionZipFile.java
 
b/plugins/actions/zipfile/src/main/java/org/apache/hop/workflow/actions/zipfile/ActionZipFile.java
index 74763a70bd..d0717e3bd8 100644
--- 
a/plugins/actions/zipfile/src/main/java/org/apache/hop/workflow/actions/zipfile/ActionZipFile.java
+++ 
b/plugins/actions/zipfile/src/main/java/org/apache/hop/workflow/actions/zipfile/ActionZipFile.java
@@ -683,7 +683,7 @@ public class ActionZipFile extends ActionBase implements 
Cloneable, IAction {
             realMoveToDirectory + Const.FILE_SEPARATOR + 
fileObjectd.getName().getBaseName(),
             getVariables())) {
 
-      fileObjectd.moveTo(fileObjectm);
+      HopVfs.moveFile(fileObjectd, fileObjectm);
     } catch (Exception e) {
       logError(
           BaseMessages.getString(PKG, "ActionZipFile.Cant_Move_File1.Label")
diff --git 
a/plugins/tech/sftp/src/main/java/org/apache/hop/pipeline/transforms/sftpput/SftpPut.java
 
b/plugins/tech/sftp/src/main/java/org/apache/hop/pipeline/transforms/sftpput/SftpPut.java
index afa0ebcad9..29ce23bf3e 100644
--- 
a/plugins/tech/sftp/src/main/java/org/apache/hop/pipeline/transforms/sftpput/SftpPut.java
+++ 
b/plugins/tech/sftp/src/main/java/org/apache/hop/pipeline/transforms/sftpput/SftpPut.java
@@ -22,7 +22,6 @@ import java.io.OutputStream;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystemException;
 import org.apache.commons.vfs2.NameScope;
-import org.apache.commons.vfs2.Selectors;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.ResultFile;
 import org.apache.hop.core.exception.HopException;
@@ -369,30 +368,21 @@ public class SftpPut extends BaseTransform<SftpPutMeta, 
SftpPutData> {
   }
 
   /**
-   * Move a file, also when the two folders sit on different file systems of 
the operating system.
-   *
-   * <p>Two local folders are one and the same file system as far as VFS is 
concerned, whichever
-   * disks they're on, so {@link FileObject#moveTo(FileObject)} always picks a 
rename. A rename
-   * across a mount point is exactly what the OS refuses to do, so fall back 
to a copy followed by a
-   * delete. See <a href="https://github.com/apache/hop/issues/5936";>issue 
#5936</a>.
+   * Move a file, saying which file it was about when that doesn't work. {@link
+   * HopVfs#moveFile(FileObject, FileObject)} is what gets it across two file 
systems of the
+   * operating system.
    */
   private void move(FileObject sourceFile, FileObject destination) throws 
HopException {
     try {
-      sourceFile.moveTo(destination);
-    } catch (FileSystemException renameFailed) {
-      try {
-        destination.copyFrom(sourceFile, Selectors.SELECT_SELF);
-        sourceFile.delete();
-      } catch (Exception copyFailed) {
-        renameFailed.addSuppressed(copyFailed);
-        throw new HopException(
-            BaseMessages.getString(
-                PKG,
-                "SftpPut.Error.UnableToMove",
-                sourceFile.getPublicURIString(),
-                destination.getPublicURIString()),
-            renameFailed);
-      }
+      HopVfs.moveFile(sourceFile, destination);
+    } catch (FileSystemException moveFailed) {
+      throw new HopException(
+          BaseMessages.getString(
+              PKG,
+              "SftpPut.Error.UnableToMove",
+              sourceFile.getPublicURIString(),
+              destination.getPublicURIString()),
+          moveFailed);
     }
   }
 
diff --git 
a/plugins/tech/sftp/src/main/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPut.java
 
b/plugins/tech/sftp/src/main/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPut.java
index d469e09e57..27fb89a95c 100644
--- 
a/plugins/tech/sftp/src/main/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPut.java
+++ 
b/plugins/tech/sftp/src/main/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPut.java
@@ -543,7 +543,7 @@ public class ActionSftpPut extends ActionBase implements 
Cloneable, IAction {
                           realDestinationFolder
                               + Const.FILE_SEPARATOR
                               + myFile.getName().getBaseName());
-                  myFile.moveTo(destination);
+                  HopVfs.moveFile(myFile, destination);
                   if (isDetailed()) {
                     logDetailed(
                         BaseMessages.getString(
diff --git 
a/plugins/tech/sftp/src/test/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPutMoveTest.java
 
b/plugins/tech/sftp/src/test/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPutMoveTest.java
new file mode 100644
index 0000000000..ab7143ffcb
--- /dev/null
+++ 
b/plugins/tech/sftp/src/test/java/org/apache/hop/workflow/actions/sftpput/ActionSftpPutMoveTest.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.workflow.actions.sftpput;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.FileStore;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.Collections;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.Selectors;
+import org.apache.hop.core.Result;
+import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.junit.vfs.CrossDeviceFileProvider;
+import org.apache.hop.workflow.Workflow;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.actions.sftpput.ActionSftpPut.AfterFtpAction;
+import org.apache.hop.workflow.engine.IWorkflowEngine;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Issue <a href="https://github.com/apache/hop/issues/5936";>#5936</a>: "move 
the file after the
+ * upload" only worked when the source folder and the destination folder 
happened to sit on the same
+ * file system of the operating system. On a machine where the working folder 
and the archive folder
+ * are two different mounts the action ended with
+ *
+ * <pre>Could not rename "file:///files/work/x.csv" to 
"file:///files/arc/x.csv"</pre>
+ *
+ * <p>{@link FileObject#moveTo(FileObject)} only falls back to a copy followed 
by a delete when the
+ * two files belong to two different VFS file systems, and two local folders 
are one and the same
+ * VFS file system whichever disk they're on - so it always picks a rename, 
which is exactly what
+ * the operating system refuses across a mount point. Hence {@link 
HopVfs#moveFile(FileObject,
+ * FileObject)}, which the action goes through.
+ *
+ * <p>The tests upload to an embedded Apache MINA SSHD server, so they 
exercise the real action from
+ * end to end.
+ */
+class ActionSftpPutMoveTest {
+
+  /** Registers the plugins and initializes the engine, which the action needs 
to run. */
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  private static final String LOCALHOST = "127.0.0.1";
+  private static final String USER = "alice";
+  private static final String PASSWORD = "secret";
+  private static final String FILE_NAME = "sales.csv";
+  private static final String PAYLOAD = "id;amount\n1;42\n";
+
+  /**
+   * A scheme served by {@link CrossDeviceFileProvider}: one VFS file system, 
on which a rename
+   * never succeeds. That is the situation of two local folders on two 
different mounts.
+   */
+  private static final String CROSS_DEVICE = "xdev";
+
+  private static final String WORK_FOLDER = CROSS_DEVICE + ":///work";
+  private static final String ARCHIVE_FOLDER = CROSS_DEVICE + ":///archive";
+
+  /** Points at a folder on a different file system than the temporary folder, 
when there is one. */
+  private static final String CROSS_DEVICE_DIR_PROPERTY = 
"hop.test.cross.device.dir";
+
+  @TempDir private Path serverRoot;
+  @TempDir private Path localRoot;
+
+  private SshServer sshServer;
+
+  @BeforeEach
+  void startServer() throws IOException {
+    sshServer = SshServer.setUpDefaultServer();
+    sshServer.setHost(LOCALHOST);
+    sshServer.setPort(0);
+    sshServer.setKeyPairProvider(
+        new SimpleGeneratorHostKeyProvider(serverRoot.resolve("hostkey.ser")));
+    
sshServer.setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
+    sshServer.setFileSystemFactory(new VirtualFileSystemFactory(serverRoot));
+    sshServer.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory()));
+    // The client keeps its session open, and the server's 10 minute default 
idle timeout would
+    // hold the test open that long.
+    CoreModuleProperties.IDLE_TIMEOUT.set(sshServer, Duration.ofSeconds(5));
+    sshServer.start();
+
+    HopVfs.getFileSystemManager().addProvider(CROSS_DEVICE, new 
CrossDeviceFileProvider());
+  }
+
+  @AfterEach
+  void stopServer() throws IOException {
+    if (sshServer != null) {
+      sshServer.stop();
+    }
+    // The provider above is registered on the one and only file system 
manager.
+    HopVfs.reset();
+  }
+
+  /**
+   * The reproduction of the report: the file is uploaded and then has to be 
moved to a folder on
+   * another file system, which is where the action used to give up.
+   */
+  @Test
+  @DisplayName("#5936: the uploaded file is moved to a folder on another file 
system")
+  void movesTheUploadedFileToAnotherFileSystem() throws Exception {
+    writeFile(WORK_FOLDER + "/" + FILE_NAME);
+    HopVfs.getFileObject(ARCHIVE_FOLDER).createFolder();
+
+    ActionSftpPut action = action(WORK_FOLDER, ARCHIVE_FOLDER);
+    Result result = action.execute(new Result(), 0);
+
+    assertEquals(0, result.getNrErrors(), () -> "the action reported an 
error:\n" + log(action));
+    assertTrue(Files.exists(serverRoot.resolve(FILE_NAME)), "the file never 
reached the server");
+    assertTrue(
+        HopVfs.getFileObject(ARCHIVE_FOLDER + "/" + FILE_NAME).exists(),
+        () -> "the uploaded file was not moved to the destination folder:\n" + 
log(action));
+    assertFalse(
+        HopVfs.getFileObject(WORK_FOLDER + "/" + FILE_NAME).exists(),
+        "the file is still in the source folder");
+  }
+
+  /**
+   * The same run with both folders on one file system, where the rename does 
work. It tells apart
+   * "the move is broken" from "the move across a mount point is broken".
+   */
+  @Test
+  @DisplayName("the uploaded file is moved to a folder on the same file 
system")
+  void movesTheUploadedFileWithinOneFileSystem() throws Exception {
+    Path work = Files.createDirectory(localRoot.resolve("work"));
+    Path archive = Files.createDirectory(localRoot.resolve("archive"));
+    Files.writeString(work.resolve(FILE_NAME), PAYLOAD);
+
+    ActionSftpPut action = action(work.toString(), archive.toString());
+    Result result = action.execute(new Result(), 0);
+
+    assertEquals(0, result.getNrErrors(), () -> "the action reported an 
error:\n" + log(action));
+    assertTrue(Files.exists(serverRoot.resolve(FILE_NAME)), "the file never 
reached the server");
+    assertTrue(Files.exists(archive.resolve(FILE_NAME)), "the uploaded file 
was not moved");
+    assertFalse(Files.exists(work.resolve(FILE_NAME)), "the file is still in 
the source folder");
+  }
+
+  /**
+   * The reproduction above once more, but on two genuinely different mounts 
instead of a file
+   * system which refuses to rename. It needs a writable folder on a second 
file store: {@code
+   * /dev/shm} on Linux, or any folder handed in through the {@code 
hop.test.cross.device.dir}
+   * system property (a mounted RAM disk, a network share, a second volume). 
Without one there is
+   * nothing to prove here and the test is skipped.
+   */
+  @Test
+  @DisplayName("#5936: the uploaded file is moved to a folder on a second real 
mount")
+  void movesTheUploadedFileToASecondMount() throws Exception {
+    Path secondMount = secondFileStore();
+    assumeTrue(
+        secondMount != null,
+        "no writable folder on a second file store; set -D" + 
CROSS_DEVICE_DIR_PROPERTY);
+
+    Path work = Files.createTempDirectory(secondMount, "hop-5936-work");
+    Path archive = Files.createDirectory(localRoot.resolve("archive"));
+    Files.writeString(work.resolve(FILE_NAME), PAYLOAD);
+
+    try {
+      ActionSftpPut action = action(work.toString(), archive.toString());
+      Result result = action.execute(new Result(), 0);
+
+      assertEquals(0, result.getNrErrors(), () -> "the action reported an 
error:\n" + log(action));
+      assertTrue(Files.exists(serverRoot.resolve(FILE_NAME)), "the file never 
reached the server");
+      assertTrue(Files.exists(archive.resolve(FILE_NAME)), "the uploaded file 
was not moved");
+      assertFalse(Files.exists(work.resolve(FILE_NAME)), "the file is still in 
the source folder");
+    } finally {
+      Files.deleteIfExists(work.resolve(FILE_NAME));
+      Files.deleteIfExists(work);
+    }
+  }
+
+  /**
+   * Why the action can't leave the move to VFS: two folders of one file 
system always take the
+   * rename branch of {@link FileObject#moveTo(FileObject)}, and a failing 
rename is the end of it -
+   * the copy plus delete branch is only for two different VFS file systems. 
Two local folders are
+   * one VFS file system whichever disks they are on, so the fallback is never 
reached, however far
+   * apart the two folders are.
+   */
+  @Test
+  @DisplayName("moveTo() renames within one file system and does not fall back 
to a copy")
+  void moveToWithinOneFileSystemNeverFallsBackToACopy() throws Exception {
+    FileObject source = writeFile(WORK_FOLDER + "/" + FILE_NAME);
+    FileObject destination = HopVfs.getFileObject(ARCHIVE_FOLDER + "/" + 
FILE_NAME);
+
+    assertTrue(
+        source.canRenameTo(destination),
+        "the two folders are one VFS file system, so moveTo() renames");
+
+    FileSystemException e =
+        assertThrows(FileSystemException.class, () -> 
source.moveTo(destination));
+    assertTrue(e.getMessage().contains("Could not rename"), e.getMessage());
+    assertFalse(destination.exists(), "moveTo() did not fall back to a copy");
+
+    // Which is all HopVfs.moveFile() has to do to get the file across.
+    destination.copyFrom(source, Selectors.SELECT_SELF);
+    source.delete();
+
+    assertTrue(destination.exists());
+    assertFalse(source.exists());
+  }
+
+  /** Two local folders are one VFS file system, whichever disk each of them 
is on. */
+  @Test
+  @DisplayName("two local folders are one VFS file system")
+  void twoLocalFoldersAreOneVfsFileSystem() throws Exception {
+    FileObject here = HopVfs.getFileObject(localRoot.toString());
+    Path secondMount = secondFileStore();
+    Path elsewhere = secondMount != null ? secondMount : 
Paths.get(System.getProperty("user.dir"));
+
+    assertEquals(
+        here.getFileSystem(),
+        HopVfs.getFileObject(elsewhere.toString()).getFileSystem(),
+        "VFS sees one file system, so moveTo() will rename between these two 
folders");
+  }
+
+  private ActionSftpPut action(String localDirectory, String 
destinationFolder) {
+    ActionSftpPut action = new ActionSftpPut("SFTP Put");
+    action.setServerName(LOCALHOST);
+    action.setServerPort(String.valueOf(sshServer.getPort()));
+    action.setUserName(USER);
+    action.setPassword(PASSWORD);
+    action.setLocalDirectory(localDirectory);
+    action.setAfterSftpAction(AfterFtpAction.MOVE);
+    action.setDestinationFolder(destinationFolder);
+    action.setCreateDestinationFolder(true);
+
+    IWorkflowEngine<WorkflowMeta> parentWorkflow = mock(Workflow.class);
+    doReturn(false).when(parentWorkflow).isStopped();
+    doReturn(LogLevel.BASIC).when(parentWorkflow).getLogLevel();
+    doReturn("unit test").when(parentWorkflow).getWorkflowName();
+    action.setParentWorkflow(parentWorkflow);
+    action.setParentWorkflowMeta(mock(WorkflowMeta.class));
+    return action;
+  }
+
+  private static FileObject writeFile(String uri) throws Exception {
+    FileObject fileObject = HopVfs.getFileObject(uri);
+    try (OutputStream outputStream = 
fileObject.getContent().getOutputStream()) {
+      outputStream.write(PAYLOAD.getBytes(UTF_8));
+    }
+    return fileObject;
+  }
+
+  /** What the action logged, so a failing assertion says why the action gave 
up. */
+  private static String log(ActionSftpPut action) {
+    return HopLogStore.getAppender().getBuffer(action.getLogChannelId(), 
false).toString();
+  }
+
+  /**
+   * A writable folder on a different file store than the temporary folder, or 
null when this
+   * machine has none to offer.
+   */
+  private Path secondFileStore() throws IOException {
+    FileStore local = Files.getFileStore(localRoot);
+    for (String candidate :
+        new String[] {System.getProperty(CROSS_DEVICE_DIR_PROPERTY), 
"/dev/shm"}) {
+      if (candidate == null) {
+        continue;
+      }
+      Path path = Paths.get(candidate);
+      if (Files.isDirectory(path)
+          && Files.isWritable(path)
+          && !local.equals(Files.getFileStore(path))) {
+        return path;
+      }
+    }
+    return null;
+  }
+}
diff --git 
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
 
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
index 889d8882eb..6d50460095 100644
--- 
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
+++ 
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
@@ -2671,7 +2671,7 @@ public class ScriptValuesAddedFunctions extends 
ScriptableObject {
               boolean destinationExists = fileDestination.exists();
               // Let's move the file...
               if ((destinationExists && overwrite) || !destinationExists) {
-                fileSource.moveTo(fileDestination);
+                HopVfs.moveFile(fileSource, fileDestination);
               }
             }
           } else {
diff --git 
a/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
 
b/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
index 70f369dfa3..96ad4a2090 100644
--- 
a/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
+++ 
b/plugins/transforms/processfiles/src/main/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFiles.java
@@ -406,7 +406,7 @@ public class ProcessFiles extends 
BaseTransform<ProcessFilesMeta, ProcessFilesDa
       if (isDataVolumeMetricEnabled()) {
         movedBytes = safeContentSize(data.sourceFile);
       }
-      data.sourceFile.moveTo(HopVfs.getFileObject(targetFilename, variables));
+      HopVfs.moveFile(data.sourceFile, HopVfs.getFileObject(targetFilename, 
variables));
       if (movedBytes > 0) {
         dataVolumeIn = (dataVolumeIn != null ? dataVolumeIn : 0L) + movedBytes;
         dataVolumeOut = (dataVolumeOut != null ? dataVolumeOut : 0L) + 
movedBytes;
diff --git 
a/plugins/transforms/zipfile/src/main/java/org/apache/hop/pipeline/transforms/zipfile/ZipFile.java
 
b/plugins/transforms/zipfile/src/main/java/org/apache/hop/pipeline/transforms/zipfile/ZipFile.java
index c523deb0db..0b4387ff4a 100644
--- 
a/plugins/transforms/zipfile/src/main/java/org/apache/hop/pipeline/transforms/zipfile/ZipFile.java
+++ 
b/plugins/transforms/zipfile/src/main/java/org/apache/hop/pipeline/transforms/zipfile/ZipFile.java
@@ -288,7 +288,7 @@ public class ZipFile extends BaseTransform<ZipFileMeta, 
ZipFileData> {
           file = HopVfs.getFileObject(targetfilename, variables);
 
           // Move file
-          data.sourceFile.moveTo(file);
+          HopVfs.moveFile(data.sourceFile, file);
 
         } finally {
           if (file != null) {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java 
b/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
index 76ad5d0cf9..862e896672 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
@@ -1739,7 +1739,7 @@ public class HopVfsFileDialog implements IFileDialog, 
IDirectoryDialog {
                         HopVfs.getFileObject(
                             HopVfs.getFilename(file.getParent()) + "/" + 
renameText.getText(),
                             variables);
-                    file.moveTo(newFile);
+                    HopVfs.moveFile(file, newFile);
                   } catch (Exception e) {
                     showError(
                         BaseMessages.getString(
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index 5c99e2bcc1..94b5251eed 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -1508,7 +1508,7 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
       }
 
       // Rename the folder
-      sourceFile.moveTo(targetFile);
+      HopVfs.moveFile(sourceFile, targetFile);
 
       // Update all opened impacted file type handlers
       for (String filename : filenames) {
@@ -1541,7 +1541,7 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
       String newPath = sourceExists ? pathFromTarget : pathFromSource;
 
       // Rename the file
-      sourceFile.moveTo(targetFile);
+      HopVfs.moveFile(sourceFile, targetFile);
 
       // Update opened file type handler (use old path to find the handler)
       TabItemHandler handler = findTabItemHandler(oldPath);
@@ -1577,7 +1577,7 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
       }
 
       // Move file
-      sourceFile.moveTo(targetFile);
+      HopVfs.moveFile(sourceFile, targetFile);
 
       // Update all opened impacted file type handlers
       for (String filename : filenames) {
@@ -1607,7 +1607,7 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
       String newPath = sourceExists ? pathFromTarget : pathFromSource;
 
       // Move file
-      sourceFile.moveTo(targetFile);
+      HopVfs.moveFile(sourceFile, targetFile);
 
       // Update opened file type handler
       TabItemHandler handler = findTabItemHandler(oldPath);

Reply via email to