diff --git a/build.gradle b/build.gradle
index 412e93e..7801d2d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -90,3 +90,11 @@ task updateBuildVersion << {
file('version.txt').text = "${updatedVersion}\n"
}
}
+
+task copyConfig(type: Copy) {
+
+ from 'conf/app/application.${env}.properties'
+ from 'conf/app/log4j2.${env}.xml'
+
+ to 'src/main/resources/'
+}
diff --git a/conf/app/application.local.properties b/conf/app/application.local.properties
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/application.properties b/conf/app/application.release.properties
similarity index 100%
rename from src/main/resources/application.properties
rename to conf/app/application.release.properties
diff --git a/src/main/resources/log4j2.xml b/conf/logging/log4j2.local.xml
similarity index 95%
rename from src/main/resources/log4j2.xml
rename to conf/logging/log4j2.local.xml
index 24bb5bd..6af558c 100644
--- a/src/main/resources/log4j2.xml
+++ b/conf/logging/log4j2.local.xml
@@ -28,7 +28,6 @@
-
diff --git a/conf/logging/log4j2.release.xml b/conf/logging/log4j2.release.xml
new file mode 100644
index 0000000..4f7aab0
--- /dev/null
+++ b/conf/logging/log4j2.release.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} - $5p - [%c{1}] - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/io/linuxserver/davos/transfer/ftp/FTPFile.java b/src/main/java/io/linuxserver/davos/transfer/ftp/FTPFile.java
index 56c3f64..36dd3bf 100644
--- a/src/main/java/io/linuxserver/davos/transfer/ftp/FTPFile.java
+++ b/src/main/java/io/linuxserver/davos/transfer/ftp/FTPFile.java
@@ -6,15 +6,15 @@ public class FTPFile {
private String name;
private long size;
- private String absolutePath;
+ private String path;
private DateTime lastModified;
private boolean directory;
- public FTPFile(String name, long size, String absolutePath, long mTime, boolean directory) {
+ public FTPFile(String name, long size, String path, long mTime, boolean directory) {
this.name = name;
this.size = size;
- this.absolutePath = absolutePath;
+ this.path = path;
this.lastModified = new DateTime(mTime);
this.directory = directory;
}
@@ -28,7 +28,7 @@ public class FTPFile {
}
public String getPath() {
- return absolutePath;
+ return path;
}
public DateTime getLastModified() {
diff --git a/src/main/java/io/linuxserver/davos/transfer/ftp/client/FTPSClient.java b/src/main/java/io/linuxserver/davos/transfer/ftp/client/FTPSClient.java
new file mode 100644
index 0000000..748aab9
--- /dev/null
+++ b/src/main/java/io/linuxserver/davos/transfer/ftp/client/FTPSClient.java
@@ -0,0 +1,8 @@
+package io.linuxserver.davos.transfer.ftp.client;
+
+public class FTPSClient extends FTPClient {
+
+ public FTPSClient() {
+ ftpClient = new org.apache.commons.net.ftp.FTPSClient("SSL", true);
+ }
+}
diff --git a/src/main/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnection.java b/src/main/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnection.java
index 43cd8ea..19f5eaa 100644
--- a/src/main/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnection.java
+++ b/src/main/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnection.java
@@ -1,37 +1,141 @@
package io.linuxserver.davos.transfer.ftp.connection;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import io.linuxserver.davos.transfer.ftp.FTPFile;
+import io.linuxserver.davos.transfer.ftp.exception.DownloadFailedException;
+import io.linuxserver.davos.transfer.ftp.exception.FileListingException;
+import io.linuxserver.davos.util.FileStreamFactory;
+import io.linuxserver.davos.util.FileUtils;
public class FTPConnection implements Connection {
+ private static final Logger LOGGER = LoggerFactory.getLogger(FTPConnection.class);
+
+ private org.apache.commons.net.ftp.FTPClient client;
+ private FileStreamFactory fileStreamFactory = new FileStreamFactory();
+ private FileUtils fileUtils = new FileUtils();
+
public FTPConnection(org.apache.commons.net.ftp.FTPClient client) {
- // TODO Auto-generated constructor stub
+ this.client = client;
}
-
+
@Override
public String currentDirectory() {
- // TODO Auto-generated method stub
- return null;
+
+ try {
+ return client.printWorkingDirectory();
+ } catch (IOException e) {
+ throw new FileListingException("Unable to print the working directory", e);
+ }
}
@Override
- public void download(FTPFile remoteFilePath, String localFilePath) {
- // TODO Auto-generated method stub
+ public void download(FTPFile file, String localFilePath) {
+ String cleanRemotePath = FileUtils.ensureTrailingSlash(file.getPath()) + file.getName();
+ String cleanLocalPath = FileUtils.ensureTrailingSlash(localFilePath);
+
+ try {
+
+ if (file.isDirectory())
+ downloadDirectoryAndContents(file, cleanLocalPath, cleanRemotePath);
+
+ else
+ doDownload(file, cleanRemotePath, cleanLocalPath);
+
+ } catch (FileNotFoundException e) {
+ throw new DownloadFailedException(
+ String.format("Unable to write to local directory %s", cleanLocalPath + file.getName()), e);
+ } catch (IOException e) {
+ throw new DownloadFailedException(String.format("Unable to download file %s", cleanRemotePath), e);
+ }
}
@Override
public List listFiles() {
- // TODO Auto-generated method stub
- return null;
+ return listFiles(currentDirectory());
}
@Override
public List listFiles(String remoteDirectory) {
- // TODO Auto-generated method stub
- return null;
+
+ List files = new ArrayList();
+
+ try {
+
+ String cleanRemoteDirectory = FileUtils.ensureTrailingSlash(remoteDirectory);
+ LOGGER.debug("Listing all files in {}", cleanRemoteDirectory);
+ org.apache.commons.net.ftp.FTPFile[] ftpFiles = client.listFiles(cleanRemoteDirectory);
+
+ for (org.apache.commons.net.ftp.FTPFile file : ftpFiles)
+ files.add(toFtpFile(file, cleanRemoteDirectory));
+
+ } catch (IOException e) {
+ throw new FileListingException(String.format("Unable to list files in directory %s", remoteDirectory), e);
+ }
+
+ return files.stream().filter(removeCurrentAndParentDirs()).collect(Collectors.toList());
}
+ private void doDownload(FTPFile file, String cleanRemotePath, String cleanLocalPath)
+ throws FileNotFoundException, IOException {
+
+ LOGGER.info("Downloading {} to {}", cleanRemotePath, cleanLocalPath);
+ LOGGER.debug("Creating output stream for file {}", cleanLocalPath + file.getName());
+ OutputStream outputStream = fileStreamFactory.createOutputStream(cleanLocalPath + file.getName());
+ boolean hasDownloaded = client.retrieveFile(cleanRemotePath, outputStream);
+ outputStream.close();
+
+ if (!hasDownloaded)
+ throw new DownloadFailedException("Server returned failure while downloading.");
+ }
+
+ private void downloadDirectoryAndContents(FTPFile file, String localDownloadFolder, String path) throws IOException {
+
+ LOGGER.info("Item {} is a directory. Will now check sub-items", file.getName());
+ List subItems = listFiles(path).stream().filter(removeCurrentAndParentDirs()).collect(Collectors.toList());
+
+ String fullLocalDownloadPath = FileUtils.ensureTrailingSlash(localDownloadFolder + file.getName());
+
+ LOGGER.debug("Creating new local directory {}", fullLocalDownloadPath);
+ fileUtils.createLocalDirectory(fullLocalDownloadPath);
+
+ for (FTPFile subItem : subItems) {
+
+ String subItemPath = FileUtils.ensureTrailingSlash(subItem.getPath()) + subItem.getName();
+
+ if (subItem.isDirectory()) {
+
+ String subLocalFilePath = FileUtils.ensureTrailingSlash(fullLocalDownloadPath);
+ downloadDirectoryAndContents(subItem, subLocalFilePath, FileUtils.ensureTrailingSlash(subItemPath));
+ }
+
+ else
+ doDownload(subItem, subItemPath, fullLocalDownloadPath);
+ }
+ }
+
+ private Predicate super FTPFile> removeCurrentAndParentDirs() {
+ return file -> !file.getName().equals(".") && !file.getName().equals("..");
+ }
+
+ private FTPFile toFtpFile(org.apache.commons.net.ftp.FTPFile ftpFile, String filePath) throws IOException {
+
+ String name = ftpFile.getName();
+ long fileSize = ftpFile.getSize();
+ long mTime = ftpFile.getTimestamp().getTime().getTime();
+ boolean isDirectory = ftpFile.isDirectory();
+
+ return new FTPFile(name, fileSize, filePath, mTime, isDirectory);
+ }
}
diff --git a/src/main/java/io/linuxserver/davos/util/FileStreamFactory.java b/src/main/java/io/linuxserver/davos/util/FileStreamFactory.java
new file mode 100644
index 0000000..4d1a140
--- /dev/null
+++ b/src/main/java/io/linuxserver/davos/util/FileStreamFactory.java
@@ -0,0 +1,17 @@
+package io.linuxserver.davos.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+
+public class FileStreamFactory {
+
+ public FileInputStream createInputStream(String filePath) throws FileNotFoundException {
+ return new FileInputStream(new File(filePath));
+ }
+
+ public FileOutputStream createOutputStream(String filePath) throws FileNotFoundException {
+ return new FileOutputStream(new File(filePath));
+ }
+}
diff --git a/src/test/java/io/linuxserver/davos/transfer/ftp/client/FTPSClientTest.java b/src/test/java/io/linuxserver/davos/transfer/ftp/client/FTPSClientTest.java
new file mode 100644
index 0000000..6c75fae
--- /dev/null
+++ b/src/test/java/io/linuxserver/davos/transfer/ftp/client/FTPSClientTest.java
@@ -0,0 +1,15 @@
+package io.linuxserver.davos.transfer.ftp.client;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+public class FTPSClientTest {
+
+ private FTPClient client = new FTPSClient();
+
+ @Test
+ public void newFtpsClientShouldCreateFTPSClientInstance() {
+ assertThat(client.ftpClient).isInstanceOf(org.apache.commons.net.ftp.FTPSClient.class);
+ }
+}
diff --git a/src/test/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnectionTest.java b/src/test/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnectionTest.java
new file mode 100644
index 0000000..ba6b302
--- /dev/null
+++ b/src/test/java/io/linuxserver/davos/transfer/ftp/connection/FTPConnectionTest.java
@@ -0,0 +1,347 @@
+package io.linuxserver.davos.transfer.ftp.connection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.InOrder;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import io.linuxserver.davos.transfer.ftp.FTPFile;
+import io.linuxserver.davos.transfer.ftp.exception.DownloadFailedException;
+import io.linuxserver.davos.transfer.ftp.exception.FileListingException;
+import io.linuxserver.davos.util.FileStreamFactory;
+import io.linuxserver.davos.util.FileUtils;
+
+public class FTPConnectionTest {
+
+ private static final String LOCAL_DIRECTORY = ".";
+ private static final String DIRECTORY_PATH = "this/is/a/directory";
+
+ @InjectMocks
+ private FTPConnection ftpConnection;
+
+ @Mock
+ private FileStreamFactory mockFileStreamFactory;
+
+ @Mock
+ private FileUtils mockFileUtils;
+
+ @Mock
+ private FileOutputStream mockFileOutputStream;
+
+ private org.apache.commons.net.ftp.FTPClient mockFtpClient;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Before
+ public void setUp() throws IOException {
+
+ mockFtpClient = mock(org.apache.commons.net.ftp.FTPClient.class);
+
+ when(mockFtpClient.changeWorkingDirectory(anyString())).thenReturn(true);
+ when(mockFtpClient.printWorkingDirectory()).thenReturn(DIRECTORY_PATH);
+ when(mockFtpClient.retrieveFile(anyString(), any(OutputStream.class))).thenReturn(true);
+
+ org.apache.commons.net.ftp.FTPFile[] files = createRemoteFTPFiles();
+
+ ftpConnection = new FTPConnection(mockFtpClient);
+
+ initMocks(this);
+
+ when(mockFtpClient.listFiles(anyString())).thenReturn(files);
+ when(mockFileStreamFactory.createOutputStream("./remote.file")).thenReturn(mockFileOutputStream);
+ }
+
+ @Test
+ public void whenListingFilesThenFtpClientListFilesMethodShouldBeCalledForCurrentWorkingDirectory() throws IOException {
+
+ ftpConnection.listFiles();
+
+ verify(mockFtpClient).listFiles("this/is/a/directory/");
+ }
+
+ @Test
+ public void ifWhenListingFilesFtpClientThrowsExceptionThenCatchAndRethrowFileListingExcepton() throws IOException {
+
+ expectedException.expect(FileListingException.class);
+ expectedException.expectMessage(is(equalTo("Unable to list files in directory " + DIRECTORY_PATH)));
+
+ when(mockFtpClient.listFiles("this/is/a/directory/")).thenThrow(new IOException());
+
+ ftpConnection.listFiles();
+ }
+
+ @Test
+ public void whenListingFilesThenFileArrayThatListFilesReturnsShouldBeConvertedToListOfFtpFilesAndReturned()
+ throws IOException {
+
+ List returnedFiles = ftpConnection.listFiles();
+
+ assertThat(returnedFiles.get(0).getName()).isEqualTo("File 1");
+ assertThat(returnedFiles.get(0).getSize()).isEqualTo(1000l);
+ assertThat(returnedFiles.get(0).getPath()).isEqualTo("this/is/a/directory/");
+ assertThat(returnedFiles.get(0).isDirectory()).isFalse();
+
+ assertThat(returnedFiles.get(1).getName()).isEqualTo("File 2");
+ assertThat(returnedFiles.get(1).getSize()).isEqualTo(2000l);
+ assertThat(returnedFiles.get(1).getPath()).isEqualTo("this/is/a/directory/");
+ assertThat(returnedFiles.get(1).isDirectory()).isTrue();
+
+ assertThat(returnedFiles.get(2).getName()).isEqualTo("File 3");
+ assertThat(returnedFiles.get(2).getSize()).isEqualTo(3000l);
+ assertThat(returnedFiles.get(2).getPath()).isEqualTo("this/is/a/directory/");
+ assertThat(returnedFiles.get(2).isDirectory()).isFalse();
+ }
+
+ @Test
+ public void returnedFtpFilesShouldHaveCorrectModifiedDateTimesAgainstThem() {
+
+ List files = ftpConnection.listFiles();
+
+ assertThat(files.get(0).getLastModified().toString("dd/MM/yyyy HH:mm:ss")).isEqualTo("19/03/2014 21:40:00");
+ assertThat(files.get(1).getLastModified().toString("dd/MM/yyyy HH:mm:ss")).isEqualTo("19/03/2014 21:40:00");
+ assertThat(files.get(2).getLastModified().toString("dd/MM/yyyy HH:mm:ss")).isEqualTo("19/03/2014 21:40:00");
+ }
+
+ @Test
+ public void whenListingFilesAndGivingRelativePathThenThatPathShouldBeUsedAlongsideCurrentWorkingDir() throws IOException {
+
+ ftpConnection.listFiles("relativePath");
+
+ verify(mockFtpClient).listFiles("relativePath/");
+ }
+
+ @Test
+ public void downloadMethodShouldCreateLocalFileStreamFromCorrectPathBasedOnRemoteFileName() throws FileNotFoundException {
+
+ FTPFile file = new FTPFile("remote.file", 0l, "path/to", 0, false);
+ ftpConnection.download(file, LOCAL_DIRECTORY);
+
+ verify(mockFileStreamFactory).createOutputStream(LOCAL_DIRECTORY + "/remote.file");
+ }
+
+ @Test
+ public void downloadMethodShouldCallOnFtpClientRetrieveFilesMethodWithRemoteFilename() throws IOException {
+
+ FTPFile file = new FTPFile("remote.file", 0l, "path/to", 0, false);
+ ftpConnection.download(file, LOCAL_DIRECTORY);
+
+ verify(mockFtpClient).retrieveFile("path/to/remote.file", mockFileOutputStream);
+ }
+
+ @Test
+ public void downloadMethodShouldThrowExceptionIfUnableToOpenStreamToLocalFile() throws IOException {
+
+ expectedException.expect(DownloadFailedException.class);
+ expectedException.expectMessage(is(equalTo("Unable to write to local directory " + LOCAL_DIRECTORY + "/remote.file")));
+
+ when(mockFtpClient.retrieveFile("path/to/remote.file", mockFileOutputStream)).thenThrow(new FileNotFoundException());
+
+ FTPFile file = new FTPFile("remote.file", 0l, "path/to", 0, false);
+ ftpConnection.download(file, LOCAL_DIRECTORY);
+ }
+
+ @Test
+ public void shouldDownloadFailForAnyReasonWhileInProgressThenCatchIOExceptionAndThrowNewDownloadFailedException()
+ throws IOException {
+
+ expectedException.expect(DownloadFailedException.class);
+ expectedException.expectMessage(is(equalTo("Unable to download file path/to/remote.file")));
+
+ when(mockFtpClient.retrieveFile("path/to/remote.file", mockFileOutputStream)).thenThrow(new IOException());
+
+ FTPFile file = new FTPFile("remote.file", 0l, "path/to", 0, false);
+ ftpConnection.download(file, LOCAL_DIRECTORY);
+ }
+
+ @Test
+ public void ifRetrieveFileMethodInClientReturnsFalseThenThrowDownloadFailedException() throws IOException {
+
+ expectedException.expect(DownloadFailedException.class);
+ expectedException.expectMessage(is(equalTo("Server returned failure while downloading.")));
+
+ when(mockFtpClient.retrieveFile("path/to/remote.file", mockFileOutputStream)).thenReturn(false);
+
+ FTPFile file = new FTPFile("remote.file", 0l, "path/to", 0, false);
+ ftpConnection.download(file, LOCAL_DIRECTORY);
+ }
+
+ @Test
+ public void printingWorkingDirectoryShouldCallOnUnderlyingClientMethodToGetCurrentDirectory() throws IOException {
+
+ ftpConnection.currentDirectory();
+
+ verify(mockFtpClient).printWorkingDirectory();
+ }
+
+ @Test
+ public void printingWorkingDirectoryShouldReturnExactlyWhatTheUnderlyingClientReturns() {
+ assertThat(ftpConnection.currentDirectory()).isEqualTo(DIRECTORY_PATH);
+ }
+
+ @Test
+ public void ifClientThrowsExceptionWhenTryingToGetWorkingDirectoryThenCatchExceptionAndRethrow() throws IOException {
+
+ expectedException.expect(FileListingException.class);
+ expectedException.expectMessage(is(equalTo("Unable to print the working directory")));
+
+ when(mockFtpClient.printWorkingDirectory()).thenThrow(new IOException());
+
+ ftpConnection.currentDirectory();
+ }
+
+ @Test
+ public void downloadShouldRecursivelyCheckFileIfFolderThenLsThatAndGetOnlyFiles() throws IOException {
+
+ initRecursiveListings();
+
+ FileOutputStream stream1 = mock(FileOutputStream.class);
+ FileOutputStream stream2 = mock(FileOutputStream.class);
+ FileOutputStream stream3 = mock(FileOutputStream.class);
+ FileOutputStream stream4 = mock(FileOutputStream.class);
+ FileOutputStream stream5 = mock(FileOutputStream.class);
+ FileOutputStream stream6 = mock(FileOutputStream.class);
+
+ when(mockFileStreamFactory.createOutputStream("some/directory/folder/file1.txt")).thenReturn(stream1);
+ when(mockFileStreamFactory.createOutputStream("some/directory/folder/file2.txt")).thenReturn(stream2);
+ when(mockFileStreamFactory.createOutputStream("some/directory/folder/directory1/file3.txt")).thenReturn(stream3);
+ when(mockFileStreamFactory.createOutputStream("some/directory/folder/directory1/directory2/file5.txt"))
+ .thenReturn(stream4);
+ when(mockFileStreamFactory.createOutputStream("some/directory/folder/directory1/directory2/file6.txt"))
+ .thenReturn(stream5);
+ when(mockFileStreamFactory.createOutputStream("some/directory/folder/directory1/file4.txt")).thenReturn(stream6);
+
+ FTPFile directory = new FTPFile("folder", 0, "path/to", 0, true);
+ ftpConnection.download(directory, "some/directory");
+
+ verify(mockFileUtils).createLocalDirectory("some/directory/folder/");
+ verify(mockFtpClient).listFiles("path/to/folder/");
+
+ verify(mockFileUtils).createLocalDirectory("some/directory/folder/directory1/");
+ verify(mockFtpClient).listFiles("path/to/folder/directory1/");
+
+ verify(mockFileUtils).createLocalDirectory("some/directory/folder/directory1/directory2/");
+ verify(mockFtpClient).listFiles("path/to/folder/directory1/directory2/");
+
+ InOrder inOrder = Mockito.inOrder(mockFtpClient, stream1, stream2, stream3, stream4, stream5, stream6);
+
+ inOrder.verify(mockFtpClient).retrieveFile("path/to/folder/file1.txt", stream1);
+ inOrder.verify(stream1).close();
+ inOrder.verify(mockFtpClient).retrieveFile("path/to/folder/file2.txt", stream2);
+ inOrder.verify(stream2).close();
+ inOrder.verify(mockFtpClient).retrieveFile("path/to/folder/directory1/file3.txt", stream3);
+ inOrder.verify(stream3).close();
+ inOrder.verify(mockFtpClient).retrieveFile("path/to/folder/directory1/directory2/file5.txt", stream4);
+ inOrder.verify(stream4).close();
+ inOrder.verify(mockFtpClient).retrieveFile("path/to/folder/directory1/directory2/file6.txt", stream5);
+ inOrder.verify(stream5).close();
+ inOrder.verify(mockFtpClient).retrieveFile("path/to/folder/directory1/file4.txt", stream6);
+ inOrder.verify(stream6).close();
+ }
+
+ private void initRecursiveListings() throws IOException {
+
+ org.apache.commons.net.ftp.FTPFile[] entries = new org.apache.commons.net.ftp.FTPFile[5];
+
+ entries[0] = (createSingleEntry(".", 123l, 1394525265, true));
+ entries[1] = (createSingleEntry("..", 123l, 1394525265, true));
+ entries[2] = (createSingleEntry("file1.txt", 123l, 1394525265, false));
+ entries[3] = (createSingleEntry("file2.txt", 456l, 1394652161, false));
+ entries[4] = (createSingleEntry("directory1", 789l, 1391879364, true));
+
+ when(mockFtpClient.listFiles("path/to/folder/")).thenReturn(entries);
+
+ org.apache.commons.net.ftp.FTPFile[] subEntries = new org.apache.commons.net.ftp.FTPFile[5];
+
+ subEntries[0] = (createSingleEntry(".", 123l, 1394525265, true));
+ subEntries[1] = (createSingleEntry("..", 123l, 1394525265, true));
+ subEntries[2] = (createSingleEntry("file3.txt", 789l, 1394525265, false));
+ subEntries[3] = (createSingleEntry("directory2", 789l, 1394525265, true));
+ subEntries[4] = (createSingleEntry("file4.txt", 789l, 1394525265, false));
+
+ when(mockFtpClient.listFiles("path/to/folder/directory1/")).thenReturn(subEntries);
+
+ org.apache.commons.net.ftp.FTPFile[] subSubEntries = new org.apache.commons.net.ftp.FTPFile[4];
+
+ subSubEntries[0] = (createSingleEntry(".", 123l, 1394525265, true));
+ subSubEntries[1] = (createSingleEntry("..", 123l, 1394525265, true));
+ subSubEntries[2] = (createSingleEntry("file5.txt", 789l, 1394525265, false));
+ subSubEntries[3] = (createSingleEntry("file6.txt", 789l, 1394525265, false));
+
+ when(mockFtpClient.listFiles("path/to/folder/directory1/directory2/")).thenReturn(subSubEntries);
+ }
+
+ private org.apache.commons.net.ftp.FTPFile createSingleEntry(String fileName, long size, int mTime, boolean directory) {
+
+ org.apache.commons.net.ftp.FTPFile file = mock(org.apache.commons.net.ftp.FTPFile.class);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(new Date(mTime));
+
+ when(file.getName()).thenReturn(fileName);
+ when(file.getTimestamp()).thenReturn(calendar);
+ when(file.getSize()).thenReturn(size);
+ when(file.isDirectory()).thenReturn(directory);
+
+ return file;
+ }
+
+ private org.apache.commons.net.ftp.FTPFile[] createRemoteFTPFiles() {
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(2014, 2, 19, 21, 40, 00);
+
+ org.apache.commons.net.ftp.FTPFile[] files = new org.apache.commons.net.ftp.FTPFile[5];
+
+ org.apache.commons.net.ftp.FTPFile currentDir = mock(org.apache.commons.net.ftp.FTPFile.class);
+ when(currentDir.getName()).thenReturn(".");
+ when(currentDir.getTimestamp()).thenReturn(calendar);
+
+ org.apache.commons.net.ftp.FTPFile parentDir = mock(org.apache.commons.net.ftp.FTPFile.class);
+ when(parentDir.getName()).thenReturn("..");
+ when(parentDir.getTimestamp()).thenReturn(calendar);
+
+ files[0] = currentDir;
+ files[1] = parentDir;
+
+ for (int i = 2; i < 5; i++) {
+
+ org.apache.commons.net.ftp.FTPFile file = mock(org.apache.commons.net.ftp.FTPFile.class);
+
+ when(file.getName()).thenReturn("File " + (i - 1));
+ when(file.getSize()).thenReturn((long) (i - 1) * 1000);
+ when(file.getTimestamp()).thenReturn(calendar);
+ when(file.isDirectory()).thenReturn(setTrueIfNumberIsEven(i));
+
+ files[i] = file;
+ }
+
+ return files;
+ }
+
+ private boolean setTrueIfNumberIsEven(int i) {
+ return (i + 1) % 2 == 0 ? true : false;
+ }
+}
\ No newline at end of file