Added patterns for schedule steps and actions

- Defined pattern for schedule workflow and steps
 - Implemented first 2 post-download actions (move, pushbullet)
This commit is contained in:
Josh Stark 2015-10-16 21:19:16 +01:00
parent 5176a12827
commit 28bc100670
17 changed files with 454 additions and 21 deletions

View File

@ -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'

View File

@ -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<String> filters = new ArrayList<String>();
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<String> getFilters() {
return filters;
}
public void setFilters(List<String> filters) {
this.filters = filters;
}
public String getScheduleName() {
return scheduleName;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,6 @@
package io.linuxserver.davos.schedule.workflow.actions;
public interface PostDownloadAction {
void execute(PostDownloadExecution execution);
}

View File

@ -0,0 +1,6 @@
package io.linuxserver.davos.schedule.workflow.actions;
public class PostDownloadExecution {
public String fileName;
}

View File

@ -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<PushbulletRequest> httpEntity = new HttpEntity<PushbulletRequest>(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 + "]";
}
}
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -1,39 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%rEx</Property>
<Property name="LOG_LEVEL_PATTERN">%highlight{%-5p}</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} - ${LOG_LEVEL_PATTERN} - [%c{1}] - %msg%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}" />
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} - %5p - [%c{1}] - %msg%n" />
</Console>
<RollingFile name="File" fileName="davos.log" filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log">
<RollingFile name="File" fileName="davos.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log">
<PatternLayout>
<Pattern>${LOG_PATTERN}</Pattern>
<Pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} - $5p - [%c{1}] - %msg%n
</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="io.linuxserver" level="debug" />
<Logger name="org.thymeleaf" level="warn" />
<Root level="info">
<Root level="debug">
<AppenderRef ref="Console" />
<AppenderRef ref="File" />
<!-- <AppenderRef ref="File" /> -->
</Root>
</Loggers>
</Configuration>

View File

@ -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);
}
}

View File

@ -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<HttpEntity<PushbulletRequest>> 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());
}
}