Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "ftp"
version = "2.13.1"
version = "2.13.2"
authors = ["Ballerina"]
keywords = ["FTP", "SFTP", "remote file", "file transfer", "client", "service"]
repository = "https://github.com/ballerina-platform/module-ballerina-ftp"
Expand Down Expand Up @@ -45,5 +45,5 @@ path = "./lib/commons-lang3-3.17.0.jar"
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "ftp-native"
version = "2.13.1"
path = "../native/build/libs/ftp-native-2.13.1.jar"
version = "2.13.2"
path = "../native/build/libs/ftp-native-2.13.2-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ id = "ftp-compiler-plugin"
class = "io.ballerina.stdlib.ftp.plugin.FtpCompilerPlugin"

[[dependency]]
path = "../compiler-plugin/build/libs/ftp-compiler-plugin-2.13.1.jar"
path = "../compiler-plugin/build/libs/ftp-compiler-plugin-2.13.2-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ distribution-version = "2201.12.0"
[[package]]
org = "ballerina"
name = "ftp"
version = "2.13.1"
version = "2.13.2"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
Expand Down
126 changes: 68 additions & 58 deletions ballerina/tests/secure_listener_endpoint_with_caller_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -39,84 +39,93 @@ ListenerConfiguration callerListenerConfig = {
}
},
port: 21213,
pollingInterval: 2,
pollingInterval: 1,
path: "/in",
fileNamePattern: "(.*).caller"
};

Service callerService = service object {
string addedFilepath = "";

remote function onFileChange(WatchEvent & readonly event, Caller caller) returns error? {
event.addedFiles.forEach(function (FileInfo fileInfo) {
self.addedFilepath = fileInfo.path;
});
if self.addedFilepath.endsWith("/put.caller") {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check caller->put("/put2.caller", bStream);
} else if self.addedFilepath.endsWith("/append.caller") {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(appendFilePath, 7);
check caller->append("/append.caller", bStream);
} else if self.addedFilepath.endsWith("/rename.caller") {
check caller->rename("/rename.caller", "/rename2.caller");
} else if self.addedFilepath.endsWith("/delete.caller") {
check caller->delete("/delete.caller");
} else if self.addedFilepath.endsWith("/list.caller") {
fileList = check caller->list("/");
} else if self.addedFilepath.endsWith("/get.caller") {
stream<io:Block, io:Error?> fileStream = check caller->get("/get.caller");
fileGetContentCorrect = check matchStreamContent(fileStream, "Put content");
} else if self.addedFilepath.endsWith("/mkdir.caller") {
check caller->mkdir("/callerDir");
} else if self.addedFilepath.endsWith("/rmdir.caller") {
check caller->rmdir("/callerDir");
} else if self.addedFilepath.endsWith("/size.caller") {
fileSize = check caller->size("/size.caller");
} else if self.addedFilepath.endsWith("/isdirectory.caller") {
isDir = check caller->isDirectory("/callerDir");
foreach FileInfo fileInfo in event.addedFiles {
string addedFilepath = fileInfo.path;
if addedFilepath.endsWith("/put.caller") {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check caller->put("/out/put2.caller", bStream);
check caller->rename("/in/put.caller", "/out/put.caller");
} else if addedFilepath.endsWith("/append.caller") {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(appendFilePath, 7);
check caller->append("/in/append.caller", bStream);
check caller->rename("/in/append.caller", "/out/append.caller");
} else if addedFilepath.endsWith("/rename.caller") {
check caller->rename("/in/rename.caller", "/out/rename.caller");
} else if addedFilepath.endsWith("/delete.caller") {
check caller->delete("/in/delete.caller");
} else if addedFilepath.endsWith("/list.caller") {
fileList = check caller->list("/in");
check caller->rename("/in/list.caller", "/out/list.caller");
} else if addedFilepath.endsWith("/get.caller") {
stream<io:Block, io:Error?> fileStream = check caller->get("/in/get.caller");
fileGetContentCorrect = check matchStreamContent(fileStream, "Put content");
check caller->rename("/in/get.caller", "/out/get.caller");
} else if addedFilepath.endsWith("/mkdir.caller") {
check caller->mkdir("/out/callerDir");
check caller->rename("/in/mkdir.caller", "/out/mkdir.caller");
} else if addedFilepath.endsWith("/rmdir.caller") {
check caller->rmdir("/out/callerDir");
check caller->rename("/in/rmdir.caller", "/out/rmdir.caller");
} else if addedFilepath.endsWith("/size.caller") {
fileSize = check caller->size("/in/size.caller");
check caller->rename("/in/size.caller", "/out/size.caller");
} else if addedFilepath.endsWith("/isdirectory.caller") {
isDir = check caller->isDirectory("/out/callerDir");
check caller->rename("/in/isdirectory.caller", "/out/isdirectory.caller");
}
}

}
};

@test:Config {}
public function testFilePutWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/put.caller", bStream);
check (<Client>sftpClientEp)->put("/in/put.caller", bStream);
runtime:sleep(3);
stream<byte[] & readonly, io:Error?> str = check (<Client>sftpClientEp)->get("/put2.caller");
stream<byte[] & readonly, io:Error?> str = check (<Client>sftpClientEp)->get("/out/put2.caller");
test:assertTrue(check matchStreamContent(str, "Put content"));
check str.close();
check (<Client>sftpClientEp)->delete("/put.caller");
check (<Client>sftpClientEp)->delete("/put2.caller");
check (<Client>sftpClientEp)->delete("/out/put2.caller");
check (<Client>sftpClientEp)->delete("/out/put.caller");
}

@test:Config {}
public function testFileAppendWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/append.caller", bStream);
check (<Client>sftpClientEp)->put("/in/append.caller", bStream);
runtime:sleep(3);
stream<byte[] & readonly, io:Error?> str = check (<Client>sftpClientEp)->get("/append.caller");
stream<byte[] & readonly, io:Error?> str = check (<Client>sftpClientEp)->get("/out/append.caller");
test:assertTrue(check matchStreamContent(str, "Put contentAppend content"));
check str.close();
check (<Client>sftpClientEp)->delete("/append.caller");
check (<Client>sftpClientEp)->delete("/out/append.caller");
}

@test:Config {}
public function testFileRenameWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/rename.caller", bStream);
check (<Client>sftpClientEp)->put("/in/rename.caller", bStream);
runtime:sleep(3);
stream<byte[] & readonly, io:Error?> str = check (<Client>sftpClientEp)->get("/rename2.caller");
stream<byte[] & readonly, io:Error?> str = check (<Client>sftpClientEp)->get("/out/rename.caller");
test:assertTrue(check matchStreamContent(str, "Put content"));
check str.close();
check (<Client>sftpClientEp)->delete("/rename2.caller");
check (<Client>sftpClientEp)->delete("/out/rename.caller");
}

@test:Config {}
public function testFileDeleteWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/delete.caller", bStream);
check (<Client>sftpClientEp)->put("/in/delete.caller", bStream);
runtime:sleep(3);
stream<byte[] & readonly, io:Error?>|Error result = (<Client>sftpClientEp)->get("/delete.caller");
stream<byte[] & readonly, io:Error?>|Error result = (<Client>sftpClientEp)->get("/in/delete.caller");
if result is Error {
test:assertTrue(result.message().endsWith("delete.caller not found"));
} else {
Expand All @@ -127,65 +136,66 @@ public function testFileDeleteWithCaller() returns error? {
@test:Config {}
public function testFileListWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/list.caller", bStream);
check (<Client>sftpClientEp)->put("/in/list.caller", bStream);
runtime:sleep(3);
test:assertEquals(fileList[0].path, "/list.caller");
check (<Client>sftpClientEp)->delete("/list.caller");
test:assertEquals(fileList.length(), 1);
test:assertEquals(fileList[0].path, "/in/list.caller");
check (<Client>sftpClientEp)->delete("/out/list.caller");
}

@test:Config {}
public function testFileGetWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/get.caller", bStream);
check (<Client>sftpClientEp)->put("/in/get.caller", bStream);
runtime:sleep(3);
test:assertTrue(fileGetContentCorrect);
check (<Client>sftpClientEp)->delete("/get.caller");
check (<Client>sftpClientEp)->delete("/out/get.caller");
}

@test:Config {}
public function testMkDirWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/mkdir.caller", bStream);
check (<Client>sftpClientEp)->put("/in/mkdir.caller", bStream);
runtime:sleep(3);
boolean isDir = check (<Client>sftpClientEp)->isDirectory("/callerDir");
boolean isDir = check (<Client>sftpClientEp)->isDirectory("/out/callerDir");
test:assertTrue(isDir);
check (<Client>sftpClientEp)->delete("/mkdir.caller");
check (<Client>sftpClientEp)->delete("/out/mkdir.caller");
}

@test:Config {
dependsOn: [testMkDirWithCaller]
}
public function testIsDirectoryWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/isdirectory.caller", bStream);
check (<Client>sftpClientEp)->put("/in/isdirectory.caller", bStream);
runtime:sleep(3);
test:assertTrue(isDir);
check (<Client>sftpClientEp)->delete("/isdirectory.caller");
check (<Client>sftpClientEp)->delete("/out/isdirectory.caller");
}

@test:Config {
dependsOn: [testIsDirectoryWithCaller]
}
public function testRmDirWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/rmdir.caller", bStream);
check (<Client>sftpClientEp)->put("/in/rmdir.caller", bStream);
runtime:sleep(3);
boolean|Error result = (<Client>sftpClientEp)->isDirectory("/callerDir");
boolean|Error result = (<Client>sftpClientEp)->isDirectory("/out/callerDir");
if result is Error {
test:assertEquals(result.message(), "/callerDir does not exists to check if it is a directory.");
test:assertEquals(result.message(), "/out/callerDir does not exists to check if it is a directory.");
} else {
test:assertFail("Expected an error");
}
check (<Client>sftpClientEp)->delete("/rmdir.caller");
check (<Client>sftpClientEp)->delete("/out/rmdir.caller");
}

@test:Config {}
public function testFileSizeWithCaller() returns error? {
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/size.caller", bStream);
check (<Client>sftpClientEp)->put("/in/size.caller", bStream);
runtime:sleep(3);
test:assertEquals(fileSize, 11);
check (<Client>sftpClientEp)->delete("/size.caller");
check (<Client>sftpClientEp)->delete("/out/size.caller");
}

@test:Config {
Expand All @@ -205,8 +215,8 @@ public function testMutableWatchEventWithCaller() returns error? {
check 'listener.attach(watchEventService);
check 'listener.start();
stream<io:Block, io:Error?> bStream = check io:fileReadBlocksAsStream(putFilePath, 5);
check (<Client>sftpClientEp)->put("/" + filename, bStream);
check (<Client>sftpClientEp)->put("/in/" + filename, bStream);
runtime:sleep(3);
test:assertEquals(addedFile, filename);
check (<Client>sftpClientEp)->delete("/" + filename);
check (<Client>sftpClientEp)->delete("/in/" + filename);
}
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ This file contains all the notable changes done to the Ballerina Email package t

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## unreleased

### Fixed
- [Fix the issue where the FTP listener stops working if the file name is the same](https://github.com/ballerina-platform/ballerina-library/issues/8035)

## [2.13.1] - 2025-04-23

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,6 @@ private void handleDirectory(FileObject[] children) throws FileSystemException {
*/
private void handleFile(FileObject file) throws FileSystemException {
String path = file.getName().getURI();
if (processed.contains(path)) {
return;
}
FileInfo info = new FileInfo(path);
info.setFileSize(file.getContent().getSize());
info.setLastModifiedTime(file.getContent().getLastModifiedTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ public static Object initSftpServer(String resources) throws Exception {
sftpServer = SshServer.setUpDefaultServer();
VirtualFileSystemFactory virtualFileSystemFactory
= new VirtualFileSystemFactory(homeFolder.getAbsoluteFile().toPath());
virtualFileSystemFactory.getDefaultHomeDir().resolve("in").toFile().mkdirs();
virtualFileSystemFactory.getDefaultHomeDir().resolve("out").toFile().mkdirs();
sftpServer.setFileSystemFactory(virtualFileSystemFactory);
sftpServer.setHost("localhost");
sftpServer.setPort(port);
Expand Down