This is an automated email from the ASF dual-hosted git repository.
mcasters 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 ec2fbe52a4 minor fixes to byte tracking, and some cleanup, fixes #6931
(#6933)
ec2fbe52a4 is described below
commit ec2fbe52a483af8f297595c86388ef08f677efd3
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Apr 2 15:19:19 2026 +0200
minor fixes to byte tracking, and some cleanup, fixes #6931 (#6933)
---
.../hop/workflow/actions/unzip/ActionUnZip.java | 26 +-
.../actions/unzip/WorkflowActionUnZipTest.java | 42 ++-
.../transforms/processfiles/ProcessFiles.java | 352 ++++++++++++++----
.../messages/messages_en_US.properties | 39 +-
.../messages/messages_es_AR.properties | 5 +-
.../messages/messages_fr_FR.properties | 6 +-
.../messages/messages_it_IT.properties | 6 +-
.../messages/messages_ja_JP.properties | 5 +-
.../messages/messages_ko_KR.properties | 4 +-
.../messages/messages_pt_BR.properties | 8 +-
.../messages/messages_zh_CN.properties | 6 +-
.../transforms/processfiles/ProcessFilesTest.java | 393 +++++++++++++++++++++
12 files changed, 762 insertions(+), 130 deletions(-)
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 6cf735450f..14b362b128 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
@@ -480,7 +480,9 @@ public class ActionUnZip extends ActionBase implements
Cloneable, IAction {
if (!children[i].getType().equals(FileType.FOLDER)) {
boolean unzip = true;
- String filename = children[i].getName().getPath();
+ // Match RegExp against the file name only (like other file
actions). getPath() is the
+ // full path from the FS root, so patterns such as
"daily_.*\\.zip" never matched.
+ String sourceMatchName = children[i].getName().getBaseName();
Pattern patternSource = null;
@@ -490,7 +492,7 @@ public class ActionUnZip extends ActionBase implements
Cloneable, IAction {
// First see if the file matches the regular expression!
if (patternSource != null) {
- Matcher matcher = patternSource.matcher(filename);
+ Matcher matcher = patternSource.matcher(sourceMatchName);
unzip = matcher.matches();
}
if (unzip) {
@@ -781,7 +783,7 @@ public class ActionUnZip extends ActionBase implements
Cloneable, IAction {
// Unzip done...
if (afterUnzip > 0) {
- doUnzipPostProcessing(sourceFileObject, movetodir,
realMovetodirectory);
+ doUnzipPostProcessing(sourceFileObject, movetodir,
realMovetodirectory, result);
}
retval = true;
} catch (Exception e) {
@@ -797,7 +799,7 @@ public class ActionUnZip extends ActionBase implements
Cloneable, IAction {
/** Moving or deleting source file. */
private void doUnzipPostProcessing(
- FileObject sourceFileObject, FileObject movetodir, String
realMovetodirectory)
+ FileObject sourceFileObject, FileObject movetodir, String
realMovetodirectory, Result result)
throws FileSystemException {
if (afterUnzip == 1) {
// delete zip file
@@ -822,8 +824,24 @@ public class ActionUnZip extends ActionBase implements
Cloneable, IAction {
movetodir + Const.FILE_SEPARATOR +
sourceFileObject.getName().getBaseName();
destFile = HopVfs.getFileObject(destinationFilename, getVariables());
+ long archiveSize = 0;
+ try {
+ if (sourceFileObject.getType().hasContent()) {
+ long sz = sourceFileObject.getContent().getSize();
+ if (sz > 0) {
+ archiveSize = sz;
+ }
+ }
+ } catch (Exception ignored) {
+ // best-effort for metrics only
+ }
+
sourceFileObject.moveTo(destFile);
+ if (archiveSize > 0) {
+ result.setBytesWrittenThisAction(result.getBytesWrittenThisAction()
+ archiveSize);
+ }
+
// File moved
if (isDetailed()) {
logDetailed(
diff --git
a/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
b/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
index d44d5c9a42..1bb603d6c1 100644
---
a/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
+++
b/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
@@ -16,11 +16,16 @@
*/
package org.apache.hop.workflow.actions.unzip;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.vfs2.FileContent;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileType;
+import org.apache.hop.core.Result;
import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
import
org.apache.hop.workflow.action.loadsave.WorkflowActionLoadSaveTestSupport;
import org.junit.jupiter.api.Test;
@@ -50,21 +55,38 @@ class WorkflowActionUnZipTest extends
WorkflowActionLoadSaveTestSupport<ActionUn
jobEntryUnZip
.getClass()
.getDeclaredMethod(
- "doUnzipPostProcessing", FileObject.class, FileObject.class,
String.class);
+ "doUnzipPostProcessing",
+ FileObject.class,
+ FileObject.class,
+ String.class,
+ Result.class);
unzipPostprocessingMethod.setAccessible(true);
- FileObject sourceFileObject = Mockito.mock(FileObject.class);
-
Mockito.doReturn(Mockito.mock(FileName.class)).when(sourceFileObject).getName();
+ FileObject movetodir = Mockito.mock(FileObject.class);
+ Mockito.when(movetodir.toString()).thenReturn("file:///dest");
// delete
+ FileObject sourceForDelete = Mockito.mock(FileObject.class);
+
Mockito.doReturn(Mockito.mock(FileName.class)).when(sourceForDelete).getName();
jobEntryUnZip.afterUnzip = 1;
- unzipPostprocessingMethod.invoke(
- jobEntryUnZip, sourceFileObject, Mockito.mock(FileObject.class), "");
- Mockito.verify(sourceFileObject, Mockito.times(1)).delete();
+ unzipPostprocessingMethod.invoke(jobEntryUnZip, sourceForDelete,
movetodir, "", new Result());
+ Mockito.verify(sourceForDelete, Mockito.times(1)).delete();
+
+ // move (bytes written for moved archive size)
+ FileObject sourceForMove = Mockito.mock(FileObject.class);
+ FileName moveName = Mockito.mock(FileName.class);
+ Mockito.when(moveName.getBaseName()).thenReturn("archive.zip");
+ Mockito.when(sourceForMove.getName()).thenReturn(moveName);
+ FileType moveType = Mockito.mock(FileType.class);
+ Mockito.when(sourceForMove.getType()).thenReturn(moveType);
+ Mockito.when(moveType.hasContent()).thenReturn(true);
+ FileContent moveContent = Mockito.mock(FileContent.class);
+ Mockito.when(sourceForMove.getContent()).thenReturn(moveContent);
+ Mockito.when(moveContent.getSize()).thenReturn(77L);
- // move
jobEntryUnZip.afterUnzip = 2;
- unzipPostprocessingMethod.invoke(
- jobEntryUnZip, sourceFileObject, Mockito.mock(FileObject.class), "");
- Mockito.verify(sourceFileObject, Mockito.times(1)).moveTo(Mockito.any());
+ Result moveResult = new Result();
+ unzipPostprocessingMethod.invoke(jobEntryUnZip, sourceForMove, movetodir,
"", moveResult);
+ Mockito.verify(sourceForMove, Mockito.times(1)).moveTo(Mockito.any());
+ assertEquals(77L, moveResult.getBytesWrittenThisAction());
}
}
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 5a29b25db5..af89338ec2 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
@@ -17,12 +17,18 @@
package org.apache.hop.pipeline.transforms.processfiles;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSelectInfo;
import org.apache.commons.vfs2.FileSelector;
import org.apache.commons.vfs2.FileType;
+import org.apache.hop.core.Const;
import org.apache.hop.core.ResultFile;
import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.io.CountingInputStream;
+import org.apache.hop.core.io.CountingOutputStream;
import org.apache.hop.core.util.Utils;
import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.i18n.BaseMessages;
@@ -34,6 +40,8 @@ import org.apache.hop.pipeline.transform.TransformMeta;
public class ProcessFiles extends BaseTransform<ProcessFilesMeta,
ProcessFilesData> {
private static final Class<?> PKG = ProcessFilesMeta.class;
+ private static final int COPY_BUFFER_SIZE = 8192;
+
public ProcessFiles(
TransformMeta transformMeta,
ProcessFilesMeta meta,
@@ -90,7 +98,6 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
}
if (meta.simulate && isBasic()) {
-
logBasic(BaseMessages.getString(PKG,
"ProcessFiles.Log.SimulationModeON"));
}
} // End If first
@@ -129,7 +136,8 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
if (data.targetFile.exists()) {
if (isDetailed()) {
logDetailed(
- BaseMessages.getString(PKG,
"ProcessFiles.Log.TargetFileExists", targetFilename));
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Log.TargetFileExists",
friendlyPath(targetFilename)));
}
// check if target is really a file otherwise it could overwrite a
complete folder by copy
// or move operations
@@ -154,6 +162,13 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
parentFolder.toString()));
} else {
parentFolder.createFolder();
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.ParentFolderCreated",
+ friendlyPath(parentFolder.toString())));
+ }
}
}
if (parentFolder != null) {
@@ -162,77 +177,29 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
}
}
+ if (isDetailed()) {
+ String targetForLog =
+ meta.getOperationType() == ProcessFilesMeta.OPERATION_TYPE_DELETE
+ ? "-"
+ : friendlyPath(targetFilename);
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.RowHeader",
+ operationTypeLabel(),
+ friendlyPath(sourceFilename),
+ targetForLog));
+ }
+
switch (meta.getOperationType()) {
case ProcessFilesMeta.OPERATION_TYPE_COPY:
- if (((meta.isOverwriteTargetFile() && data.targetFile.exists())
- || !data.targetFile.exists())
- && !meta.simulate) {
- if (meta.isOverwriteTargetFile() && data.targetFile.exists())
- if (isBasic()) {
- logBasic(
- BaseMessages.getString(
- PKG,
- "ProcessFiles.Log.SourceFileCopied.TargetOverwritten",
- targetFilename));
- }
-
- // Better to delete the file before because. sometime, it's not
properly overwritten
- data.targetFile.delete();
- data.targetFile.copyFrom(data.sourceFile, new
TextOneToOneFileSelector());
-
- if (isDetailed()) {
- logDetailed(
- BaseMessages.getString(
- PKG, "ProcessFiles.Log.SourceFileCopied",
sourceFilename, targetFilename));
- }
- } else {
- if (isDetailed()) {
- logDetailed(
- BaseMessages.getString(
- PKG,
- "ProcessFiles.Log.TargetNotOverwritten",
- sourceFilename,
- targetFilename));
- }
- }
+ processCopyOperation(sourceFilename, targetFilename);
break;
case ProcessFilesMeta.OPERATION_TYPE_MOVE:
- if (((meta.isOverwriteTargetFile() && data.targetFile.exists())
- || !data.targetFile.exists())
- && !meta.simulate) {
- if (meta.isOverwriteTargetFile() && data.targetFile.exists())
- if (isBasic()) {
- logBasic(
- BaseMessages.getString(
- PKG,
"ProcessFiles.Log.SourceFileMoved.TargetOverwritten", targetFilename));
- }
- data.sourceFile.moveTo(HopVfs.getFileObject(targetFilename,
variables));
- if (isDetailed()) {
- logDetailed(
- BaseMessages.getString(
- PKG, "ProcessFiles.Log.SourceFileMoved", sourceFilename,
targetFilename));
- }
- } else {
- if (isDetailed()) {
- logDetailed(
- BaseMessages.getString(
- PKG,
- "ProcessFiles.Log.TargetNotOverwritten",
- sourceFilename,
- targetFilename));
- }
- }
+ processMoveOperation(sourceFilename, targetFilename);
break;
case ProcessFilesMeta.OPERATION_TYPE_DELETE:
- if (!meta.simulate && !data.sourceFile.delete()) {
- throw new HopException(
- BaseMessages.getString(
- PKG, "ProcessFiles.Error.CanNotDeleteFile",
data.sourceFile.toString()));
- }
- if (isDetailed()) {
- logDetailed(
- BaseMessages.getString(PKG,
"ProcessFiles.Log.SourceFileDeleted", sourceFilename));
- }
+ processDeleteOperation(sourceFilename);
break;
default:
break;
@@ -255,14 +222,17 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
if (isDetailed()) {
logDetailed(
BaseMessages.getString(
- PKG, "ProcessFiles.Log.FilenameAddResult",
data.targetFile.toString()));
+ PKG,
+ "ProcessFiles.Log.FilenameAddResult",
+ friendlyPath(data.targetFile.toString())));
}
}
putRow(getInputRowMeta(), r); // copy row to possible alternate
rowset(s).
if (checkFeedback(getLinesRead()) && isBasic()) {
- logBasic(BaseMessages.getString(PKG, "ProcessFiles.LineNumber") +
getLinesRead());
+ logBasic(
+ BaseMessages.getString(PKG, "ProcessFiles.LineNumber",
Long.toString(getLinesRead())));
}
} catch (Exception e) {
boolean sendToErrorRow = false;
@@ -273,7 +243,7 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
errorMessage = e.toString();
} else {
logError(
- BaseMessages.getString(PKG,
"ProcessFiles.ErrorInTransformRunning") + e.getMessage());
+ BaseMessages.getString(PKG,
"ProcessFiles.ErrorInTransformRunning", e.getMessage()), e);
setErrors(1);
stopAll();
setOutputDone(); // signal end to receiver(s)
@@ -288,6 +258,248 @@ public class ProcessFiles extends
BaseTransform<ProcessFilesMeta, ProcessFilesDa
return true;
}
+ private String operationTypeLabel() {
+ switch (meta.getOperationType()) {
+ case ProcessFilesMeta.OPERATION_TYPE_COPY:
+ return BaseMessages.getString(PKG,
"ProcessFilesMeta.operationType.Copy");
+ case ProcessFilesMeta.OPERATION_TYPE_MOVE:
+ return BaseMessages.getString(PKG,
"ProcessFilesMeta.operationType.Move");
+ case ProcessFilesMeta.OPERATION_TYPE_DELETE:
+ return BaseMessages.getString(PKG,
"ProcessFilesMeta.operationType.Delete");
+ default:
+ return "?";
+ }
+ }
+
+ /** Friendly URI for log lines (variables resolved). */
+ private String friendlyPath(String path) {
+ if (Utils.isEmpty(path)) {
+ return "";
+ }
+ try {
+ return HopVfs.getFriendlyURI(path, variables);
+ } catch (Exception e) {
+ return path;
+ }
+ }
+
+ private void processCopyOperation(String sourceFilename, String
targetFilename) throws Exception {
+ boolean targetFileExists =
+ data.targetFile.exists() && data.targetFile.getType() == FileType.FILE;
+ boolean shouldPerform =
+ !data.targetFile.exists() || (meta.isOverwriteTargetFile() &&
targetFileExists);
+
+ if (meta.simulate) {
+ if (shouldPerform && isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.WouldCopy",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ if (!shouldPerform && isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.SimulateSkipCopyExists",
+ friendlyPath(targetFilename),
+ friendlyPath(sourceFilename)));
+ }
+ return;
+ }
+
+ if (shouldPerform) {
+ if (meta.isOverwriteTargetFile() && data.targetFile.exists() &&
isBasic()) {
+ logBasic(
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Log.Basic.OverwriteWarningCopy",
friendlyPath(targetFilename)));
+ }
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.CopyStarted",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ // Delete target first; some providers do not truncate correctly on
overwrite.
+ data.targetFile.delete();
+ if (isDataVolumeMetricEnabled()) {
+ copyFileTrackingVolume(data.sourceFile, data.targetFile);
+ } else {
+ data.targetFile.copyFrom(data.sourceFile, new
TextOneToOneFileSelector());
+ }
+ if (isBasic()) {
+ logBasic(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Basic.FileCopied",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.FileCopied",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ } else if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.SkippedCopyExistsNoOverwrite",
+ friendlyPath(targetFilename),
+ friendlyPath(sourceFilename)));
+ }
+ }
+
+ private void processMoveOperation(String sourceFilename, String
targetFilename) throws Exception {
+ boolean targetFileExists =
+ data.targetFile.exists() && data.targetFile.getType() == FileType.FILE;
+ boolean shouldPerform =
+ !data.targetFile.exists() || (meta.isOverwriteTargetFile() &&
targetFileExists);
+
+ if (meta.simulate) {
+ if (shouldPerform && isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.WouldMove",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ if (!shouldPerform && isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.SimulateSkipMoveExists",
+ friendlyPath(targetFilename),
+ friendlyPath(sourceFilename)));
+ }
+ return;
+ }
+
+ if (shouldPerform) {
+ if (meta.isOverwriteTargetFile() && data.targetFile.exists() &&
isBasic()) {
+ logBasic(
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Log.Basic.OverwriteWarningMove",
friendlyPath(targetFilename)));
+ }
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.MoveStarted",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ long movedBytes = 0;
+ if (isDataVolumeMetricEnabled()) {
+ movedBytes = safeContentSize(data.sourceFile);
+ }
+ data.sourceFile.moveTo(HopVfs.getFileObject(targetFilename, variables));
+ if (movedBytes > 0) {
+ dataVolumeIn = (dataVolumeIn != null ? dataVolumeIn : 0L) + movedBytes;
+ dataVolumeOut = (dataVolumeOut != null ? dataVolumeOut : 0L) +
movedBytes;
+ }
+ if (isBasic()) {
+ logBasic(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Basic.FileMoved",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.FileMoved",
+ friendlyPath(sourceFilename),
+ friendlyPath(targetFilename)));
+ }
+ } else if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ProcessFiles.Log.Detail.SkippedMoveExistsNoOverwrite",
+ friendlyPath(targetFilename),
+ friendlyPath(sourceFilename)));
+ }
+ }
+
+ private void processDeleteOperation(String sourceFilename) throws Exception {
+ if (meta.simulate) {
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Log.Detail.WouldDelete",
friendlyPath(sourceFilename)));
+ }
+ return;
+ }
+ if (!data.sourceFile.delete()) {
+ throw new HopException(
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Error.CanNotDeleteFile",
data.sourceFile.toString()));
+ }
+ if (isBasic()) {
+ logBasic(
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Log.Basic.FileDeleted",
friendlyPath(sourceFilename)));
+ }
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG, "ProcessFiles.Log.Detail.FileDeleted",
friendlyPath(sourceFilename)));
+ }
+ }
+
+ private boolean isDataVolumeMetricEnabled() {
+ return getPipeline() != null
+ &&
Const.toBoolean(getPipeline().getVariable(Const.HOP_METRIC_DATA_VOLUME, "N"));
+ }
+
+ private void copyFileTrackingVolume(FileObject source, FileObject
destination)
+ throws IOException {
+ FileObject parent = destination.getParent();
+ if (parent != null && !parent.exists()) {
+ parent.createFolder();
+ }
+ long read;
+ long written;
+ try (InputStream rawIn = HopVfs.getInputStream(source);
+ CountingInputStream in = new CountingInputStream(rawIn);
+ OutputStream rawOut = HopVfs.getOutputStream(destination, false);
+ CountingOutputStream out = new CountingOutputStream(rawOut)) {
+ byte[] buffer = new byte[COPY_BUFFER_SIZE];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ if (len > 0) {
+ out.write(buffer, 0, len);
+ }
+ }
+ out.flush();
+ read = in.getCount();
+ written = out.getCount();
+ }
+ dataVolumeIn = (dataVolumeIn != null ? dataVolumeIn : 0L) + read;
+ dataVolumeOut = (dataVolumeOut != null ? dataVolumeOut : 0L) + written;
+ }
+
+ private static long safeContentSize(FileObject file) {
+ try {
+ if (file != null && file.getType().hasContent()) {
+ return Math.max(0L, file.getContent().getSize());
+ }
+ } catch (Exception ignored) {
+ // metrics only
+ }
+ return 0L;
+ }
+
private class TextOneToOneFileSelector implements FileSelector {
@Override
public boolean includeFile(FileSelectInfo info) throws Exception {
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
index 162b4ecb71..7abe95f679 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_en_US.properties
@@ -24,8 +24,8 @@ ProcessFiles.Error.SourceFileNotFile=Source file [{0}] is not
a file (probably a
ProcessFiles.Error.TargetFileEmpty=Target filename is empty\!
ProcessFiles.Error.TargetFilenameFieldMissing=Target Filename is missing\!
ProcessFiles.Error.TargetFileNotFile=Target file [{0}] must be given as a file
(it is probably an existing folder)\!
-ProcessFiles.Error.TargetParentFolderNotExists=We can not fin target parent
folder [{0}]\!
-ProcessFiles.ErrorInTransformRunning=Because of an error, this transform
can''t continue\:
+ProcessFiles.Error.TargetParentFolderNotExists=Cannot find target parent
folder [{0}]. Enable ''Create target parent folder'' or create it before
running.
+ProcessFiles.ErrorInTransformRunning=Process files transform stopped: {0}
ProcessFiles.Exception.CouldnotFindField=Couldn''t find field ''{0}'' in row\!
ProcessFiles.Injection.AddResultFilenames=Add target filenames to result (Y/N)
ProcessFiles.Injection.CreateParentFolder=Create parent folder (Y/N)
@@ -34,17 +34,30 @@ ProcessFiles.Injection.OverwriteTargetFile=Overwrite target
files (Y/N)
ProcessFiles.Injection.Simulate=Turn on simulation mode (Y/N)
ProcessFiles.Injection.SourceFilenameField=Source filename fieldname
ProcessFiles.Injection.TargetFilenameField=Target filename fieldname
-ProcessFiles.LineNumber=linenr {0}
-ProcessFiles.Log.FileAddedResult=File was read by a Process files transform
-ProcessFiles.Log.FilenameAddResult=Filename [{0}] was added to result
filenames.
-ProcessFiles.Log.SimulationModeON=SIMULATION MODE ON \: WE WILL NOT
COPY/MOVE/DELETE FILES.
-ProcessFiles.Log.SourceFileCopied=Source file [{0}] was copied to [{1}]
-ProcessFiles.Log.SourceFileCopied.TargetOverwritten=Target file {0} will be
overwritten during copy operation.
-ProcessFiles.Log.SourceFileDeleted=Source file [{0}] was deleted.
-ProcessFiles.Log.SourceFileMoved=Source file [{0}] was moved to [{1}].
-ProcessFiles.Log.SourceFileMoved.TargetOverwritten=Target file {0} will be
overwritten during move operation.
-ProcessFiles.Log.TargetFileExists=Target file [{0}] exists.
-ProcessFiles.Log.TargetNotOverwritten=The file {1} was not overwritten by {0}.
+ProcessFiles.LineNumber=Process files: processed {0} input rows
+ProcessFiles.Log.Basic.FileCopied=Copied file {0} to {1}
+ProcessFiles.Log.Basic.FileDeleted=Deleted file {0}
+ProcessFiles.Log.Basic.FileMoved=Moved file {0} to {1}
+ProcessFiles.Log.Basic.OverwriteWarningCopy=Copy will replace existing target
file {0}
+ProcessFiles.Log.Basic.OverwriteWarningMove=Move will replace existing target
file {0}
+ProcessFiles.Log.Detail.CopyStarted=Starting copy: {0} -> {1}
+ProcessFiles.Log.Detail.FileCopied=Finished copy: {0} -> {1}
+ProcessFiles.Log.Detail.FileDeleted=Finished delete: {0}
+ProcessFiles.Log.Detail.FileMoved=Finished move: {0} -> {1}
+ProcessFiles.Log.Detail.MoveStarted=Starting move: {0} -> {1}
+ProcessFiles.Log.Detail.ParentFolderCreated=Created missing target parent
folder {0}
+ProcessFiles.Log.Detail.RowHeader=Row: operation={0}, source={1}, target={2}
+ProcessFiles.Log.Detail.SimulateSkipCopyExists=Simulation: skip copy — target
{0} exists and overwrite is off; source {1} unchanged
+ProcessFiles.Log.Detail.SimulateSkipMoveExists=Simulation: skip move — target
{0} exists and overwrite is off; source {1} unchanged
+ProcessFiles.Log.Detail.SkippedCopyExistsNoOverwrite=Skipped copy: target {0}
exists and overwrite is disabled; left source {1} unchanged
+ProcessFiles.Log.Detail.SkippedMoveExistsNoOverwrite=Skipped move: target {0}
exists and overwrite is disabled; left source {1} unchanged
+ProcessFiles.Log.Detail.WouldCopy=Simulation: would copy {0} to {1} (checks
passed)
+ProcessFiles.Log.Detail.WouldDelete=Simulation: would delete {0} (checks
passed)
+ProcessFiles.Log.Detail.WouldMove=Simulation: would move {0} to {1} (checks
passed)
+ProcessFiles.Log.FilenameAddResult=Added to pipeline result file list: {0}
+ProcessFiles.Log.FileAddedResult=File added to pipeline results by Process
files
+ProcessFiles.Log.SimulationModeON=Simulation mode on — this transform will not
copy, move, or delete files
+ProcessFiles.Log.TargetFileExists=Target path exists (file): {0}
ProcessFiles.Name=Process files
ProcessFilesDialog.AddResult.Label=Add target filename to result
ProcessFilesDialog.AddResult.Tooltip=Add target filename to result
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_es_AR.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_es_AR.properties
index 7caf98d981..c6c3dad7db 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_es_AR.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_es_AR.properties
@@ -25,15 +25,12 @@ ProcessFiles.Error.SourceFileNotFile=El archivo fuente
[{0}] no es un archivo (p
ProcessFiles.Error.TargetFileEmpty=\u00A1Falta el nombre del archivo destino\!
ProcessFiles.Error.TargetFilenameFieldMissing=\u00A1Falta el nombre del
archivo destino\!
ProcessFiles.Error.TargetParentFolderNotExists=El directorio padre destino
[{0}] no puede ser encontrado.
-ProcessFiles.ErrorInTransformRunning=Este paso no puede continuar debido a un
error\:
+ProcessFiles.ErrorInTransformRunning=Este paso no puede continuar debido a un
error\: {0}
ProcessFiles.Exception.CouldnotFindField=El campo "{0}" no puede ser
encontrado en la fila.
ProcessFiles.LineNumber=Nro l\u00EDnea {0}
ProcessFiles.Log.FileAddedResult=El archivo fue le\u00EDdo por un paso
"Procesar archivos"
ProcessFiles.Log.FilenameAddResult=El archivo [{0}] fue a\u00F1adido al
listado de archvios resultado.
ProcessFiles.Log.SimulationModeON=MODO DE SIMULACI\u00D3N ACTIVADO\: NO SE
COPIAR\u00C1N/MOVER\u00C1N/ELIMINAR\u00C1N ARCHIVOS
-ProcessFiles.Log.SourceFileCopied=El archivo fuente [{0}] fue copiado a [{1}]
-ProcessFiles.Log.SourceFileDeleted=El archivo fuente [{0}] fue eliminado.
-ProcessFiles.Log.SourceFileMoved=El archivo fuente [{0}] fue movido a [{1}]
ProcessFiles.Log.TargetFileExists=El archivo destino [{0}] existe.
ProcessFilesDialog.AddResult.Label=A\u00F1adir el archivo destino al resultado
ProcessFilesDialog.AddResult.Tooltip=A\u00F1adir el archivo destino al listado
de archivos resultado
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_fr_FR.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_fr_FR.properties
index 3f8139c632..8f6543fd74 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_fr_FR.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_fr_FR.properties
@@ -27,17 +27,13 @@ ProcessFiles.Error.TargetFileEmpty=Le fichier cible est
vide\!
ProcessFiles.Error.TargetFilenameFieldMissing=Le champ contenant le nom du
fichier cible n''a pas \u00E9t\u00E9 sp\u00E9cifi\u00E9\!
ProcessFiles.Error.TargetFileNotFile=[{0}] n''est pas un fichier (probablement
un r\u00E9pertoire)\!
ProcessFiles.Error.TargetParentFolderNotExists=Impossible de trouver le
r\u00E9pertoire parent cible [{0}]\!
-ProcessFiles.ErrorInTransformRunning=Apache Hop a rencontr\u00E9 une erreur
lors de l''ex\u00E9cution de cette transformation\:
+ProcessFiles.ErrorInTransformRunning=Apache Hop a rencontr\u00E9 une erreur
lors de l''ex\u00E9cution de cette transformation\: {0}
ProcessFiles.Exception.CouldnotFindField=Impossible de trouver le champ
''{0}'' dans le flux d''entr\u00E9e\!
ProcessFiles.LineNumber=N\u00B0Ligne {0}
ProcessFiles.Log.FileAddedResult=Ce fichier a \u00E9t\u00E9 lu par une
\u00E9tape ''Traitement fichiers''
ProcessFiles.Log.FilenameAddResult=Le nom de fichier [{0}] a \u00E9t\u00E9
ajout\u00E9 \u00E0 la liste des noms de fichiere li\u00E9e au r\u00E9sultat.
ProcessFiles.Log.SimulationModeON=LA SIMULATION A ETE ACTIVEE : LES FICHIERS
NE SERONT NI COPIER/DEPLACER/SUPPRIMER.
-ProcessFiles.Log.SourceFileCopied=Le fichier source [{0}] a \u00E9t\u00E9
copi\u00E9 vers [{1}]
-ProcessFiles.Log.SourceFileDeleted=Le ficher [{0}] a \u00E9t\u00E9 supprimer.
-ProcessFiles.Log.SourceFileMoved=Le fichier source [{0}] a \u00E9t\u00E9
d\u00E9plac\u00E9 vers [{1}]
ProcessFiles.Log.TargetFileExists=Le fichier cible [{0}] existe.
-ProcessFiles.Log.TargetNotOverwritten=Le fichier {1} n''a pas \u00E9t\u00E9
remplac\u00E9 par le fichier {0}.
ProcessFiles.Name=Traitement fichiers
ProcessFilesDialog.AddResult.Label=Ajouter nom fichier cible au r\u00E9sultat
ProcessFilesDialog.AddResult.Tooltip=Ajouter le nom du fichier cible\n\u00E0
la liste des noms de fichier li\u00E9 au r\u00E9sultat.
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_it_IT.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_it_IT.properties
index 8e8334068b..31b37429fe 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_it_IT.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_it_IT.properties
@@ -26,17 +26,13 @@ ProcessFiles.Error.TargetFileEmpty=Il nome del file di
destinazione \u00E8 vuoto
ProcessFiles.Error.TargetFilenameFieldMissing=Manca il nome del file di
destinazione\!
ProcessFiles.Error.TargetFileNotFile=La destinazione [{0}] deve essere data
come un file (probabilmente \u00E8 una cartella esistente)\!
ProcessFiles.Error.TargetParentFolderNotExists=Impossibile trovare la cartella
padre per la posizione di destinazione [{0}]\!
-ProcessFiles.ErrorInTransformRunning=A causa di un errore, questo transform
non pu\u00F2 contuinuare\:
+ProcessFiles.ErrorInTransformRunning=A causa di un errore, questo transform
non pu\u00F2 contuinuare\: {0}
ProcessFiles.Exception.CouldnotFindField=Impossibile trovare il campo ''{0}''
nella riga\!
ProcessFiles.LineNumber=linea n\u00B0 {0}
ProcessFiles.Log.FileAddedResult=Il file \u00E8 stato letto dal transform
Process file
ProcessFiles.Log.FilenameAddResult=Il nome file [{0}] \u00E8 stato aggiunto al
nome file del risultato.
ProcessFiles.Log.SimulationModeON=MODALITA'' SIMULAZIONE ATTIVATA\: I FILE NON
VERRANNO COPIATI/SPOSTATI/CANCELLATI.
-ProcessFiles.Log.SourceFileCopied=Il file sorgente [{0}] \u00E8 stato copiato
in [{1}]
-ProcessFiles.Log.SourceFileDeleted=Il file sorgente [{0}] \u00E8 stato
cancellato.
-ProcessFiles.Log.SourceFileMoved=Il file sorgente [{0}] \u00E8 stato spostato
in [{1}]
ProcessFiles.Log.TargetFileExists=Il file di destinazione [{0}] esiste.
-ProcessFiles.Log.TargetNotOverwritten=Il file {1} non \u00E8 stato
sovrascritto da {0}.
ProcessFilesDialog.AddResult.Label=Aggiungi il nome file di destinazione al
risultato
ProcessFilesDialog.AddResult.Tooltip=Aggiungi il nome file di destinazione al
risultato
ProcessFilesDialog.CreateParentFolder.Label=Crea la cartella padre di
destinazione
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ja_JP.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ja_JP.properties
index 0fa19aec99..95565ddfea 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ja_JP.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ja_JP.properties
@@ -26,15 +26,12 @@ ProcessFiles.Error.SourceFileNotFile=Source file [{0}] is
not a file (probably a
ProcessFiles.Error.TargetFileEmpty=Target filename is empty\!
ProcessFiles.Error.TargetFilenameFieldMissing=Target Filename is missing\!
ProcessFiles.Error.TargetParentFolderNotExists=We can not fin target parent
folder [{0}]\!
-ProcessFiles.ErrorInTransformRunning=Because of an error, this transform
can''t continue\:
+ProcessFiles.ErrorInTransformRunning=Because of an error, this transform
can''t continue\: {0}
ProcessFiles.Exception.CouldnotFindField=Couldn''t find field ''{0}'' in row\!
ProcessFiles.LineNumber=linenr {0}
ProcessFiles.Log.FileAddedResult=File was read by a Process files transform
ProcessFiles.Log.FilenameAddResult=Filename [{0}] was added to result
filenames.
ProcessFiles.Log.SimulationModeON=SIMULATION MODE ON \: WE WILL NOT
COPY/MOVE/DELETE FILES.
-ProcessFiles.Log.SourceFileCopied=Source file [{0}] was copied to [{1}]
-ProcessFiles.Log.SourceFileDeleted=Source file [{0}] was deleted.
-ProcessFiles.Log.SourceFileMoved=Source file [{0}] was moved to [{1}]
ProcessFiles.Log.TargetFileExists=Target file [{0}] exists.
ProcessFiles.Name=\u30D5\u30A1\u30A4\u30EB\u51E6\u7406
ProcessFilesDialog.AddResult.Label=\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u7D50\u679C\u306B\u542B\u3080
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ko_KR.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ko_KR.properties
index d6b9fbc972..8f84d2657c 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ko_KR.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_ko_KR.properties
@@ -24,11 +24,9 @@ ProcessFiles.Error.SourceFileNotExist=\uC18C\uC2A4
\uD30C\uC77C [{0}]\uC744 \uCC
ProcessFiles.Error.SourceFileNotFile=\uC18C\uC2A4 \uD30C\uC77C [{0}]\uB294
\uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4.(\uD3F4\uB354?)\!
ProcessFiles.Error.TargetFileEmpty=\uB300\uC0C1 \uD30C\uC77C\uC774\uB984\uC744
\uC785\uB825\uD558\uC2ED\uC2DC\uC624\!
ProcessFiles.Error.TargetFilenameFieldMissing=\uAC1C\uC0C1
\uD30C\uC77C\uC774\uB984\uC744 \uC785\uB825\uD558\uC2ED\uC2DC\uC624\!
-ProcessFiles.ErrorInTransformRunning=\uC624\uB958 \uB54C\uBB38\uC5D0,
\uACC4\uC18D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4\:
+ProcessFiles.ErrorInTransformRunning=\uC624\uB958 \uB54C\uBB38\uC5D0,
\uACC4\uC18D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4\: {0}
ProcessFiles.Exception.CouldnotFindField=\uB85C\uC6B0\uC5D0\uC11C \uD544\uB4DC
''{0}''\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4\!
ProcessFiles.LineNumber=\uC904\uBC88\uD638 {0}
-ProcessFiles.Log.SourceFileDeleted=\uC18C\uC2A4 \uD30C\uC77C [{0}] \uB97C
\uC0AD\uC81C\uD558\uC600\uC2B5\uB2C8\uB2E4.
-ProcessFiles.Log.SourceFileMoved=\uC18C\uC2A4 \uD30C\uC77C [{0}]\uB294
[{1}]\uB85C \uC774\uB3D9\uD558\uC600\uC2B5\uB2C8\uB2E4
ProcessFiles.Log.TargetFileExists=\uB300\uC0C1 \uD30C\uC77C
[{0}]\uC774(\uAC00) \uC874\uC7AC\uD569\uB2C8\uB2E4.
ProcessFilesDialog.AddResult.Label=\uACB0\uACFC\uC5D0 \uB300\uC0C1
\uD30C\uC77C\uC774\uB984 \uCD94\uAC00
ProcessFilesDialog.AddResult.Tooltip=\uACB0\uACFC\uC5D0 \uB300\uC0C1
\uD30C\uC77C\uC774\uB984 \uCD94\uAC00
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_pt_BR.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_pt_BR.properties
index e33188680f..fbbf8b9fd2 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_pt_BR.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_pt_BR.properties
@@ -24,7 +24,7 @@ ProcessFiles.Error.SourceFileNotExist=Arquivo fonte[{0}]
n\u00E3o encontrado
ProcessFiles.Error.SourceFileNotFile=Fonte [{0}] n\u00E3o \u00E9 um arquivo (
provavelmente \u00E9 uma pasta ) !
ProcessFiles.Error.TargetFileEmpty=Nome de arquivo destino vazio!
ProcessFiles.Error.TargetFilenameFieldMissing=Nome de arquivo destino vazio!
-ProcessFiles.ErrorInTransformRunning=Por causa de um erro esta
transforma\u00E7\u00E3o n\u00E3o pode continuar:
+ProcessFiles.ErrorInTransformRunning=Por causa de um erro esta
transforma\u00E7\u00E3o n\u00E3o pode continuar: {0}
ProcessFiles.Exception.CouldnotFindField=N\u00E3o foi poss\u00EDvel encontrar
o campo ''{0}'' na linha !
ProcessFiles.Injection.AddResultFilenames=adicionar arquivos alvo ao resultado
ProcessFiles.Injection.CreateParentFolder=criar pasta superior
@@ -35,7 +35,6 @@ ProcessFiles.Injection.SourceFilenameField=nome do campo para
nome do arquivo de
ProcessFiles.Injection.TargetFilenameField=nome do campo para nome do arquivo
destino
ProcessFiles.LineNumber=N\u00FAmero da linha {0}
ProcessFiles.Log.FilenameAddResult=Nome de arquivo [{0}] foi adicionado ao
resultado.
-ProcessFiles.Log.SourceFileDeleted=Arquivo de origem [{0}] foi deletado
ProcessFiles.Log.TargetFileExists=arquivo destino [{0}] existe.
ProcessFiles.Name=Processar arquivos
ProcessFilesDialog.AddResult.Label=adicionar arquivo alvo ao resultado
@@ -65,11 +64,6 @@ ProcessFiles.Error.TargetFileNotFile=O arquivo alvo deve ser
dado como um arquiv
ProcessFiles.Error.TargetParentFolderNotExists=Não podemos achar a pasta dos
pais!
ProcessFiles.Log.FileAddedResult=O arquivo foi lido por um processo de
transformação.
ProcessFiles.Log.SimulationModeON=Não vamos copiar, mover ou eliminar arquivos.
-ProcessFiles.Log.SourceFileCopied=Arquivo fonte foi copiado para
-ProcessFiles.Log.SourceFileCopied.TargetOverwritten=O arquivo alvo será
substituído durante a operação de cópia.
-ProcessFiles.Log.SourceFileMoved=O arquivo fonte foi movido para.
-ProcessFiles.Log.SourceFileMoved.TargetOverwritten=O arquivo alvo será
substituído durante a operação.
-ProcessFiles.Log.TargetNotOverwritten=O arquivo não foi substituído por ele.
ProcessFilesDialog.Simulate.Tooltip=Se você selecionar esta opção, os arquivos
não serão copiados/movidos/eliminados.\r\nChecagem será feita nos arquivos
(existência, pasta superior) e mensagem exibida.
ProcessFilesMeta.CheckResult.SourceFileFieldMissing=O campo de nome de arquivo
fonte está ausente!
ProcessFilesMeta.CheckResult.SourceFileFieldOK=O campo Nome do arquivo fonte
foi especificado.
diff --git
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_zh_CN.properties
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_zh_CN.properties
index 638248faf7..c04f20032c 100644
---
a/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_zh_CN.properties
+++
b/plugins/transforms/processfiles/src/main/resources/org/apache/hop/pipeline/transforms/processfiles/messages/messages_zh_CN.properties
@@ -27,17 +27,13 @@
ProcessFiles.Error.TargetFileEmpty=\u76EE\u6807\u6587\u4EF6\u540D\u4E3A\u7A7A\!
ProcessFiles.Error.TargetFilenameFieldMissing=\u7F3A\u5931\u76EE\u6807\u6587\u4EF6\u540D\!
ProcessFiles.Error.TargetFileNotFile=\u6307\u5B9A\u76EE\u6807\u8DEF\u5F84
[{0}]
\u5FC5\u9700\u4E3A\u666E\u901A\u6587\u4EF6(\u53EF\u80FD\u5B58\u5728\u540C\u540D\u76EE\u5F55)\!
ProcessFiles.Error.TargetParentFolderNotExists=\u76EE\u6807\u8DEF\u5F84 [{0}]
\u7684\u4E0A\u7EA7\u76EE\u5F55\u4E0D\u5B58\u5728\!
-ProcessFiles.ErrorInTransformRunning=\u56E0\u51FA\u9519, \u7EC8\u6B62\:
+ProcessFiles.ErrorInTransformRunning=\u56E0\u51FA\u9519, \u7EC8\u6B62\: {0}
ProcessFiles.Exception.CouldnotFindField=\u5728\u6570\u636E\u884C\u4E2D\u627E\u4E0D\u5230\u5B57\u6BB5
"{0}"\!
ProcessFiles.LineNumber=\u884C\u53F7 {0}
ProcessFiles.Log.FileAddedResult=\u5DF2\u8BFB\u53D6\u6587\u4EF6
ProcessFiles.Log.FilenameAddResult=\u6587\u4EF6 [{0}]
\u5DF2\u6DFB\u52A0\u5230\u8FD4\u56DE\u6587\u4EF6\u5217\u8868
ProcessFiles.Log.SimulationModeON=\u5F00\u542F\u6A21\u62DF\u6A21\u5F0F\:
\u5E76\u4E0D\u4F1A\u771F\u6B63 \u590D\u5236/\u79FB\u52A8/\u5220\u9664
\u6587\u4EF6
-ProcessFiles.Log.SourceFileCopied=\u6E90\u6587\u4EF6 [{0}] \u590D\u5236\u5230
[{1}]
-ProcessFiles.Log.SourceFileDeleted=\u5DF2\u5220\u9664\u6E90\u6587\u4EF6 [{0}]
-ProcessFiles.Log.SourceFileMoved=\u6E90\u6587\u4EF6 [{0}] \u79FB\u52A8\u5230
[{1}]
ProcessFiles.Log.TargetFileExists=\u5B58\u5728\u76EE\u6807\u6587\u4EF6 [{0}]
-ProcessFiles.Log.TargetNotOverwritten=\u6587\u4EF6 {0}
\u6CA1\u6709\u8986\u76D6\u5230 {1}
ProcessFiles.Name=\u5904\u7406\u6587\u4EF6
ProcessFilesDialog.AddResult.Label=\u7ED3\u679C\u4E2D\u6DFB\u52A0\u76EE\u6807\u6587\u4EF6\u540D\:
ProcessFilesDialog.AddResult.Tooltip=\u5C06\u76EE\u6807\u6587\u4EF6\u6DFB\u52A0\u5230\u7ED3\u679C\u6587\u4EF6,
\u4FBF\u4E8E\u540E\u9762\u7684\u5904\u7406,
\u5982\u4F5C\u4E3A\u90AE\u4EF6\u9644\u4EF6.
diff --git
a/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
b/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
new file mode 100644
index 0000000000..6b0f0555f9
--- /dev/null
+++
b/plugins/transforms/processfiles/src/test/java/org/apache/hop/pipeline/transforms/processfiles/ProcessFilesTest.java
@@ -0,0 +1,393 @@
+/*
+ * 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.pipeline.transforms.processfiles;
+
+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.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Constructor;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hop.core.BlockingRowSet;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.Pipeline;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.PipelineTestingUtil;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+
+class ProcessFilesTest {
+
+ private static final String SOURCE_FIELD = "source";
+ private static final String TARGET_FIELD = "target";
+
+ @RegisterExtension
+ static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
+
+ private TransformMockHelper<ProcessFilesMeta, ProcessFilesData> helper;
+
+ @BeforeAll
+ static void initHop() throws Exception {
+ HopEnvironment.init();
+ }
+
+ @BeforeEach
+ void setUp() {
+ helper =
+ new TransformMockHelper<>(
+ "ProcessFilesTest", ProcessFilesMeta.class,
ProcessFilesData.class);
+ when(helper.logChannelFactory.create(any(), any(ILoggingObject.class)))
+ .thenReturn(helper.iLogChannel);
+
when(helper.logChannelFactory.create(any())).thenReturn(helper.iLogChannel);
+ when(helper.pipeline.isRunning()).thenReturn(true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ helper.cleanUp();
+ }
+
+ private ProcessFiles createTransform(ProcessFilesMeta meta, ProcessFilesData
data)
+ throws Exception {
+ Constructor<ProcessFiles> kons =
+ ProcessFiles.class.getConstructor(
+ TransformMeta.class,
+ ProcessFilesMeta.class,
+ ProcessFilesData.class,
+ int.class,
+ PipelineMeta.class,
+ Pipeline.class);
+ return kons.newInstance(
+ helper.transformMeta, meta, data, 0, helper.pipelineMeta,
helper.pipeline);
+ }
+
+ private RowMeta rowMeta() {
+ RowMeta rm = new RowMeta();
+ rm.addValueMeta(new ValueMetaString(SOURCE_FIELD));
+ rm.addValueMeta(new ValueMetaString(TARGET_FIELD));
+ return rm;
+ }
+
+ private static String vfsPath(Path path) {
+ return path.toUri().toString();
+ }
+
+ private void wireInput(ProcessFiles transform, RowMeta rm, Object[]... rows)
{
+ BlockingRowSet input = new BlockingRowSet(Math.max(16, rows.length + 1));
+ for (Object[] row : rows) {
+ input.putRow(rm, row);
+ }
+ input.setDone();
+ transform.setInputRowSets(new
ArrayList<>(Collections.singletonList(input)));
+ }
+
+ private ProcessFilesMeta baseMeta(int operationType) {
+ ProcessFilesMeta meta = new ProcessFilesMeta();
+ meta.setDefault();
+ meta.setSimulate(false);
+ meta.setOperationType(operationType);
+ meta.setSourceFilenameField(SOURCE_FIELD);
+ meta.setTargetFilenameField(TARGET_FIELD);
+ meta.setCreateParentFolder(false);
+ meta.setOverwriteTargetFile(false);
+ meta.setAddResultFilenames(false);
+ return meta;
+ }
+
+ @Test
+ void copy_writesTargetAndPassesRowThrough(@TempDir Path tempDir) throws
Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("out.txt");
+ Files.writeString(source, "hello-processfiles", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ List<Object[]> out = PipelineTestingUtil.execute(transform, 1, false);
+ PipelineTestingUtil.assertResult(
+ new Object[] {vfsPath(source), vfsPath(target)}, out.getFirst());
+
+ assertTrue(Files.exists(target));
+ assertEquals("hello-processfiles", Files.readString(target,
StandardCharsets.UTF_8));
+ assertTrue(Files.exists(source));
+ }
+
+ @Test
+ void copy_createsParentFolderWhenConfigured(@TempDir Path tempDir) throws
Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("nested/dir/out.txt");
+ Files.writeString(source, "x", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setCreateParentFolder(true);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertTrue(Files.isRegularFile(target));
+ assertEquals("x", Files.readString(target, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void copy_simulate_doesNotCreateTarget(@TempDir Path tempDir) throws
Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("out.txt");
+ Files.writeString(source, "only-source", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setSimulate(true);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertFalse(Files.exists(target));
+ }
+
+ @Test
+ void copy_skipsWhenTargetExistsAndOverwriteDisabled(@TempDir Path tempDir)
throws Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("out.txt");
+ Files.writeString(source, "new-content", StandardCharsets.UTF_8);
+ Files.writeString(target, "old-content", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setOverwriteTargetFile(false);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertEquals("old-content", Files.readString(target,
StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void copy_overwritesWhenEnabled(@TempDir Path tempDir) throws Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("out.txt");
+ Files.writeString(source, "replacement", StandardCharsets.UTF_8);
+ Files.writeString(target, "stale", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setOverwriteTargetFile(true);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertEquals("replacement", Files.readString(target,
StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void move_removesSourceAndWritesTarget(@TempDir Path tempDir) throws
Exception {
+ Path source = tempDir.resolve("move-src.txt");
+ Path target = tempDir.resolve("move-dst.txt");
+ Files.writeString(source, "moved-bytes", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_MOVE);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertFalse(Files.exists(source));
+ assertEquals("moved-bytes", Files.readString(target,
StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void delete_removesSource(@TempDir Path tempDir) throws Exception {
+ Path source = tempDir.resolve("del.txt");
+ Files.writeString(source, "bye", StandardCharsets.UTF_8);
+ Path dummyTarget = tempDir.resolve("ignored.txt");
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_DELETE);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source),
vfsPath(dummyTarget)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertFalse(Files.exists(source));
+ }
+
+ @Test
+ void delete_simulate_leavesSource(@TempDir Path tempDir) throws Exception {
+ Path source = tempDir.resolve("keep.txt");
+ Files.writeString(source, "still-here", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_DELETE);
+ meta.setSimulate(true);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source),
vfsPath(tempDir.resolve("dummy.txt"))});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertTrue(Files.exists(source));
+ }
+
+ @Test
+ void addResultFilenames_registersTargetAfterCopy(@TempDir Path tempDir)
throws Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("result.txt");
+ Files.writeString(source, "r", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setAddResultFilenames(true);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertEquals(1, transform.getResultFiles().size());
+
assertTrue(transform.getResultFiles().containsKey(target.toUri().toString()));
+ }
+
+ @Test
+ void copy_withDataVolumeVariable_tracksVolume(@TempDir Path tempDir) throws
Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("out.txt");
+ String payload = "metric-payload";
+ Files.writeString(source, payload, StandardCharsets.UTF_8);
+
+ helper.pipeline.setVariable(Const.HOP_METRIC_DATA_VOLUME, "Y");
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ PipelineTestingUtil.execute(transform, 1, false);
+
+ assertEquals(payload.length(), (int) (long) transform.getDataVolumeIn());
+ assertEquals(payload.length(), (int) (long) transform.getDataVolumeOut());
+ }
+
+ @Test
+ void throwsWhenSourceFilenameFieldNotSet(@TempDir Path tempDir) throws
Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("out.txt");
+ Files.writeString(source, "x", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setSourceFilenameField(null);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ assertThrows(HopException.class, () ->
PipelineTestingUtil.execute(transform, 1, false));
+ }
+
+ @Test
+ void missingParentWithoutCreateFolder_sendsRowToErrorWhenConfigured(@TempDir
Path tempDir)
+ throws Exception {
+ Path source = tempDir.resolve("in.txt");
+ Path target = tempDir.resolve("no_such_parent/out.txt");
+ Files.writeString(source, "x", StandardCharsets.UTF_8);
+
+ when(helper.transformMeta.isDoingErrorHandling()).thenReturn(true);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ meta.setCreateParentFolder(false);
+ ProcessFiles transform = spy(createTransform(meta, new
ProcessFilesData()));
+ doNothing()
+ .when(transform)
+ .putError(
+ any(IRowMeta.class),
+ any(Object[].class),
+ anyLong(),
+ anyString(),
+ nullable(String.class),
+ anyString());
+
+ RowMeta rm = rowMeta();
+ wireInput(transform, rm, new Object[] {vfsPath(source), vfsPath(target)});
+
+ assertTrue(transform.processRow());
+ verify(transform)
+ .putError(
+ any(IRowMeta.class),
+ any(Object[].class),
+ anyLong(),
+ anyString(),
+ nullable(String.class),
+ anyString());
+ assertFalse(Files.exists(target));
+ }
+
+ @Test
+ void processesMultipleRows(@TempDir Path tempDir) throws Exception {
+ Path s1 = tempDir.resolve("a.txt");
+ Path t1 = tempDir.resolve("a.out");
+ Path s2 = tempDir.resolve("b.txt");
+ Path t2 = tempDir.resolve("b.out");
+ Files.writeString(s1, "1", StandardCharsets.UTF_8);
+ Files.writeString(s2, "2", StandardCharsets.UTF_8);
+
+ ProcessFilesMeta meta = baseMeta(ProcessFilesMeta.OPERATION_TYPE_COPY);
+ ProcessFiles transform = createTransform(meta, new ProcessFilesData());
+ RowMeta rm = rowMeta();
+ wireInput(
+ transform,
+ rm,
+ new Object[] {vfsPath(s1), vfsPath(t1)},
+ new Object[] {vfsPath(s2), vfsPath(t2)});
+
+ List<Object[]> out = PipelineTestingUtil.execute(transform, 2, false);
+ PipelineTestingUtil.assertResult(new Object[] {vfsPath(s1), vfsPath(t1)},
out.get(0));
+ PipelineTestingUtil.assertResult(new Object[] {vfsPath(s2), vfsPath(t2)},
out.get(1));
+ assertEquals("1", Files.readString(t1, StandardCharsets.UTF_8));
+ assertEquals("2", Files.readString(t2, StandardCharsets.UTF_8));
+ }
+}