diff --git a/build.gradle b/build.gradle index 0c10d7a..952c16d 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,7 @@ dependencies { compile 'com.jcraft:jsch:0.1.50' compile 'joda-time:joda-time:2.3' compile 'commons-net:commons-net:3.3' + compile 'commons-io:commons-io:2.4' runtime 'com.h2database:h2' diff --git a/src/main/java/io/linuxserver/davos/schedule/ScheduleConfiguration.java b/src/main/java/io/linuxserver/davos/schedule/ScheduleConfiguration.java new file mode 100644 index 0000000..670a072 --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/ScheduleConfiguration.java @@ -0,0 +1,65 @@ +package io.linuxserver.davos.schedule; + +import java.util.ArrayList; +import java.util.List; + +import io.linuxserver.davos.transfer.ftp.TransferProtocol; +import io.linuxserver.davos.transfer.ftp.client.UserCredentials; + +public class ScheduleConfiguration { + + private TransferProtocol connectionType; + private String hostname; + private int port; + private UserCredentials credentials; + private String remoteFilePath; + private String localFilePath; + private String scheduleName; + private List filters = new ArrayList(); + + public ScheduleConfiguration(final String scheduleName, final TransferProtocol protocol, final String hostname, + final int port, final UserCredentials credentials, final String remoteFilePath, final String localFilePath) { + + this.scheduleName = scheduleName; + this.connectionType = protocol; + this.hostname = hostname; + this.port = port; + this.credentials = credentials; + } + + public TransferProtocol getConnectionType() { + return connectionType; + } + + public String getHostName() { + return hostname; + } + + public int getPort() { + return port; + } + + public UserCredentials getCredentials() { + return credentials; + } + + public String getRemoteFilePath() { + return remoteFilePath; + } + + public String getLocalFilePath() { + return localFilePath; + } + + public List getFilters() { + return filters; + } + + public void setFilters(List filters) { + this.filters = filters; + } + + public String getScheduleName() { + return scheduleName; + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/ScheduleWorkflow.java b/src/main/java/io/linuxserver/davos/schedule/workflow/ScheduleWorkflow.java new file mode 100644 index 0000000..7d082dc --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/ScheduleWorkflow.java @@ -0,0 +1,30 @@ +package io.linuxserver.davos.schedule.workflow; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.linuxserver.davos.schedule.ScheduleConfiguration; +import io.linuxserver.davos.schedule.workflow.steps.ConnectWorkflowStep; + +public class ScheduleWorkflow implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleWorkflow.class); + + private ScheduleConfiguration config; + + public ScheduleWorkflow(ScheduleConfiguration config) { + this.config = config; + } + + @Override + public void run() { + + LOGGER.info("Running schedule: {}", config.getScheduleName()); + new ConnectWorkflowStep().runSchedule(this); + LOGGER.info("Finished schedule run: {}", config.getScheduleName()); + } + + public ScheduleConfiguration getConfig() { + return config; + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/actions/MoveFileAction.java b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/MoveFileAction.java new file mode 100644 index 0000000..3ee740d --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/MoveFileAction.java @@ -0,0 +1,39 @@ +package io.linuxserver.davos.schedule.workflow.actions; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.linuxserver.davos.util.FileUtils; + +public class MoveFileAction implements PostDownloadAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(MoveFileAction.class); + + private String currentFilePath; + private String newFilePath; + + private FileUtils fileUtils = new FileUtils(); + + public MoveFileAction(String currentFilePath, String newFilePath) { + + this.currentFilePath = FileUtils.ensureTrailingSlash(currentFilePath); + this.newFilePath = FileUtils.ensureTrailingSlash(newFilePath); + } + + @Override + public void execute(PostDownloadExecution execution) { + + try { + + LOGGER.info("Executing move action: Moving {} to {}", execution.fileName, newFilePath); + fileUtils.moveFileToDirectory(currentFilePath + execution.fileName, newFilePath); + + } catch (IOException e) { + + LOGGER.error("Unable to move {} to {}. Reason given: {}", execution.fileName, newFilePath, e.getMessage()); + LOGGER.debug("Full stack trace on error", e); + } + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PostDownloadAction.java b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PostDownloadAction.java new file mode 100644 index 0000000..2dde25a --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PostDownloadAction.java @@ -0,0 +1,6 @@ +package io.linuxserver.davos.schedule.workflow.actions; + +public interface PostDownloadAction { + + void execute(PostDownloadExecution execution); +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PostDownloadExecution.java b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PostDownloadExecution.java new file mode 100644 index 0000000..a1665d9 --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PostDownloadExecution.java @@ -0,0 +1,6 @@ +package io.linuxserver.davos.schedule.workflow.actions; + +public class PostDownloadExecution { + + public String fileName; +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PushbulletNotifyAction.java b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PushbulletNotifyAction.java new file mode 100644 index 0000000..63b9656 --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/actions/PushbulletNotifyAction.java @@ -0,0 +1,65 @@ +package io.linuxserver.davos.schedule.workflow.actions; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +public class PushbulletNotifyAction implements PostDownloadAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(PushbulletNotifyAction.class); + private RestTemplate restTemplate = new RestTemplate(); + private String apiKey; + + public PushbulletNotifyAction(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public void execute(PostDownloadExecution execution) { + + PushbulletRequest body = new PushbulletRequest(); + body.body = execution.fileName; + body.title = "A new file has been downloaded"; + body.type = "note"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.add("Authorization", "Bearer " + apiKey); + + try { + + LOGGER.info("Sending notification to Pushbullet for {}", execution.fileName); + HttpEntity httpEntity = new HttpEntity(body, headers); + LOGGER.debug("Sending message to Pushbullet: {}", httpEntity); + restTemplate.exchange("https://api.pushbullet.com/v2/pushes", HttpMethod.POST, httpEntity, PushbulletResponse.class); + + } catch (RestClientException e) { + LOGGER.debug("Full stacktrace", e); + LOGGER.error("Unable to complete notification to Pushbullet. Given error: {}", e.getMessage()); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + class PushbulletResponse { + + } + + class PushbulletRequest { + + public String type; + public String title; + public String body; + + @Override + public String toString() { + return "PushbulletRequest [type=" + type + ", title=" + title + ", body=" + body + "]"; + } + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/steps/ConnectWorkflowStep.java b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/ConnectWorkflowStep.java new file mode 100644 index 0000000..70b909a --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/ConnectWorkflowStep.java @@ -0,0 +1,14 @@ +package io.linuxserver.davos.schedule.workflow.steps; + +import io.linuxserver.davos.schedule.workflow.ScheduleWorkflow; + +public class ConnectWorkflowStep extends WorkflowStep { + + public ConnectWorkflowStep() { + this.nextStep = new FilterFilesWorkflowStep(); + } + + @Override + public void runSchedule(ScheduleWorkflow schedule) { + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/steps/DisconnectWorkflowStep.java b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/DisconnectWorkflowStep.java new file mode 100644 index 0000000..e031881 --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/DisconnectWorkflowStep.java @@ -0,0 +1,10 @@ +package io.linuxserver.davos.schedule.workflow.steps; + +import io.linuxserver.davos.schedule.workflow.ScheduleWorkflow; + +public class DisconnectWorkflowStep extends WorkflowStep { + + @Override + public void runSchedule(ScheduleWorkflow schedule) { + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/steps/DownloadFilesWorkflowStep.java b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/DownloadFilesWorkflowStep.java new file mode 100644 index 0000000..bc6a49a --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/DownloadFilesWorkflowStep.java @@ -0,0 +1,14 @@ +package io.linuxserver.davos.schedule.workflow.steps; + +import io.linuxserver.davos.schedule.workflow.ScheduleWorkflow; + +public class DownloadFilesWorkflowStep extends WorkflowStep { + + public DownloadFilesWorkflowStep() { + this.nextStep = new DisconnectWorkflowStep(); + } + + @Override + public void runSchedule(ScheduleWorkflow schedule) { + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/steps/FilterFilesWorkflowStep.java b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/FilterFilesWorkflowStep.java new file mode 100644 index 0000000..5cc2c2a --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/FilterFilesWorkflowStep.java @@ -0,0 +1,14 @@ +package io.linuxserver.davos.schedule.workflow.steps; + +import io.linuxserver.davos.schedule.workflow.ScheduleWorkflow; + +public class FilterFilesWorkflowStep extends WorkflowStep { + + public FilterFilesWorkflowStep() { + this.nextStep = new DownloadFilesWorkflowStep(); + } + + @Override + public void runSchedule(ScheduleWorkflow schedule) { + } +} diff --git a/src/main/java/io/linuxserver/davos/schedule/workflow/steps/WorkflowStep.java b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/WorkflowStep.java new file mode 100644 index 0000000..88f28f4 --- /dev/null +++ b/src/main/java/io/linuxserver/davos/schedule/workflow/steps/WorkflowStep.java @@ -0,0 +1,10 @@ +package io.linuxserver.davos.schedule.workflow.steps; + +import io.linuxserver.davos.schedule.workflow.ScheduleWorkflow; + +public abstract class WorkflowStep { + + protected WorkflowStep nextStep; + + abstract public void runSchedule(ScheduleWorkflow schedule); +} diff --git a/src/main/java/io/linuxserver/davos/util/FileUtils.java b/src/main/java/io/linuxserver/davos/util/FileUtils.java new file mode 100644 index 0000000..6236dce --- /dev/null +++ b/src/main/java/io/linuxserver/davos/util/FileUtils.java @@ -0,0 +1,23 @@ +package io.linuxserver.davos.util; + +import java.io.File; +import java.io.IOException; + +public class FileUtils { + + public File getFile(String filePath) { + return new File(filePath); + } + + public void moveFileToDirectory(String oldPath, String newPath) throws IOException { + org.apache.commons.io.FileUtils.moveToDirectory(getFile(oldPath), getFile(newPath), true); + } + + public static String ensureTrailingSlash(String path) { + + if (!path.endsWith("/")) + return path + "/"; + + return path; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2e35274..94a93c5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,4 +2,4 @@ #spring.datasource.username=sa #spring.datasource.password=sa #spring.datasource.driverClassName=org.h2.Driver -#spring.jpa.hibernate.ddl-auto=none +spring.jpa.hibernate.ddl-auto=update diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 708d5b4..755ff37 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,39 +1,36 @@ - - - %rEx - %highlight{%-5p} - %d{yyyy-MM-dd HH:mm:ss.SSS} - ${LOG_LEVEL_PATTERN} - [%c{1}] - %msg%n - - + - + - + - - + + - ${LOG_PATTERN} + + %d{yyyy-MM-dd HH:mm:ss.SSS} - $5p - [%c{1}] - %msg%n + - + - + - + - - + + - + - + - + \ No newline at end of file diff --git a/src/test/java/io/linuxserver/davos/schedule/workflow/actions/MoveFileActionTest.java b/src/test/java/io/linuxserver/davos/schedule/workflow/actions/MoveFileActionTest.java new file mode 100644 index 0000000..d593352 --- /dev/null +++ b/src/test/java/io/linuxserver/davos/schedule/workflow/actions/MoveFileActionTest.java @@ -0,0 +1,51 @@ +package io.linuxserver.davos.schedule.workflow.actions; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import io.linuxserver.davos.util.FileUtils; + +public class MoveFileActionTest { + + @InjectMocks + private MoveFileAction moveFileAction = new MoveFileAction("oldPath", "newPath"); + + @Mock + private FileUtils mockFileUtils; + + @Before + public void setUp() { + initMocks(this); + } + + @Test + public void executeShouldMoveTheFile() throws IOException { + + PostDownloadExecution execution = new PostDownloadExecution(); + execution.fileName = "filename"; + + moveFileAction.execute(execution); + + verify(mockFileUtils).moveFileToDirectory("oldPath/filename", "newPath/"); + } + + @Test + public void ifMovingOfFileFailsThenDoNotPerpetuateError() throws IOException { + + doThrow(new IOException()).when(mockFileUtils).moveFileToDirectory(anyString(), anyString()); + + PostDownloadExecution execution = new PostDownloadExecution(); + execution.fileName = "filename"; + + moveFileAction.execute(execution); + } +} diff --git a/src/test/java/io/linuxserver/davos/schedule/workflow/actions/PushbulletNotifyActionTest.java b/src/test/java/io/linuxserver/davos/schedule/workflow/actions/PushbulletNotifyActionTest.java new file mode 100644 index 0000000..a703c89 --- /dev/null +++ b/src/test/java/io/linuxserver/davos/schedule/workflow/actions/PushbulletNotifyActionTest.java @@ -0,0 +1,88 @@ +package io.linuxserver.davos.schedule.workflow.actions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import io.linuxserver.davos.schedule.workflow.actions.PushbulletNotifyAction.PushbulletRequest; +import io.linuxserver.davos.schedule.workflow.actions.PushbulletNotifyAction.PushbulletResponse; + +public class PushbulletNotifyActionTest { + + @InjectMocks + private PushbulletNotifyAction pushbulletNotifyAction; + + @Mock + private RestTemplate mockRestTemplate; + + @Captor + private ArgumentCaptor> entityCaptor; + + @Before + public void setUp() { + + pushbulletNotifyAction = new PushbulletNotifyAction("apiKey"); + + initMocks(this); + } + + @Test + public void executeShouldSendCorrectData() { + + PostDownloadExecution execution = new PostDownloadExecution(); + execution.fileName = "filename"; + + pushbulletNotifyAction.execute(execution); + + verify(mockRestTemplate).exchange(eq("https://api.pushbullet.com/v2/pushes"), eq(HttpMethod.POST), entityCaptor.capture(), + eq(PushbulletResponse.class)); + + PushbulletRequest request = entityCaptor.getValue().getBody(); + + assertThat(request.type).isEqualTo("note"); + assertThat(request.title).isEqualTo("A new file has been downloaded"); + assertThat(request.body).isEqualTo("filename"); + } + + @Test + public void postDataShouldHaveCorrectHeaderValue() { + + PostDownloadExecution execution = new PostDownloadExecution(); + execution.fileName = "filename"; + + pushbulletNotifyAction.execute(execution); + + verify(mockRestTemplate).exchange(eq("https://api.pushbullet.com/v2/pushes"), eq(HttpMethod.POST), entityCaptor.capture(), + eq(PushbulletResponse.class)); + + HttpHeaders headers = entityCaptor.getValue().getHeaders(); + + assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_JSON); + assertThat(headers.get("Authorization").get(0)).isEqualTo("Bearer apiKey"); + } + + @Test + public void ifRestTemplateFailsThenDoNothing() { + + when(mockRestTemplate.exchange(eq("https://api.pushbullet.com/v2/pushes"), eq(HttpMethod.POST), any(HttpEntity.class), + eq(PushbulletResponse.class))).thenThrow(new RestClientException("")); + + pushbulletNotifyAction.execute(new PostDownloadExecution()); + } +}