Compare commits

...

9 Commits

Author SHA1 Message Date
Josh Stark
fd3a46bffa
Update application.properties 2021-12-17 10:34:35 +00:00
Josh Stark
aa3370ab46
Update application.properties 2021-12-17 10:34:21 +00:00
Josh Stark
55e2936f6c
Update README.md 2021-12-17 10:32:08 +00:00
Josh Stark
ff846da002 Update Log4j version to account for CVE-2021-44228 2021-12-17 10:21:06 +00:00
Josh Stark
9f0ba7d920 Merge pull request #24 from linuxserver/2.2.1_features
2.2.1 features
2017-10-01 15:20:13 +01:00
Josh Stark
36015655aa Update schedules.rst 2017-08-02 19:30:28 +01:00
Josh Stark
8816a2d3fa Allow resolution of $filename in URLs 2017-08-02 19:29:19 +01:00
Josh Stark
8c6adeedd3 Update schedules.rst 2017-08-02 19:17:31 +01:00
Josh Stark
59fd51f0e4 Updated version, refactored ScheduleService 2017-07-30 12:59:42 +01:00
14 changed files with 338 additions and 45 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ overridedb.*
davos.log
src/main/resources/application.properties
src/main/resources/log4j2.xml
.vscode/

View File

@ -38,6 +38,13 @@ Finally, schedules can be started or stopped at any point, using the schedules l
![https://raw.githubusercontent.com/linuxserver/davos/master/docs/list.PNG](https://raw.githubusercontent.com/linuxserver/davos/master/docs/list.PNG)
# Changelog
- **2.2.2**
- Updated log4j dependency to 2.16.0, accounting for CVE-2021-44228
- **2.2.1**
- Fixed bug where lastRunTime got reset whenever a change was made to a schedule.
- General refactoring of code, plus added unit tests.
- Allow $filename resolution in URLs of API calls.
- **2.2.0**
- The filter pattern matcher now resolves '*' to zero or more characters, rather than one or more.

View File

@ -61,9 +61,9 @@ dependencies {
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-jdbc'
compile 'org.apache.logging.log4j:log4j-api:2.4.1'
compile 'org.apache.logging.log4j:log4j-core:2.4.1'
compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.4.1'
compile 'org.apache.logging.log4j:log4j-api:2.16.0'
compile 'org.apache.logging.log4j:log4j-core:2.16.0'
compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.16.0'
compile 'com.jcraft:jsch:0.1.50'
compile 'joda-time:joda-time:2.3'

View File

@ -1 +1 @@
davos.version=2.2.0
davos.version=2.2.2

View File

@ -4,4 +4,4 @@ spring.datasource.password=sa
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update
davos.version=2.2.0
davos.version=2.2.2

View File

@ -170,8 +170,7 @@ You will need an `Amazon AWS <https://aws.amazon.com/>`_ account to use this fea
API Calls
=========
For actions that are more than just notifications, you can provide a web hook to another
application with a basic HTTP request.
API Calls are a great way to create hooks in to other applications via their own HTTP API.
URL
The URL of the API you wish to call
@ -180,9 +179,9 @@ application with a basic HTTP request.
Available options are *GET*, *POST*, *PUT* and *DELETE*
Content-Type
A header value in the request that informs the target API what type of body you're sending (if any)
Informs the target API what type of body you're sending (if any), e.g. "application/json"
Message Body
The request payload being sent to the target API
.. note:: If you need to reference the downloaded file in an HTTP request, use **$fileame**.
.. note:: If you need to reference the downloaded file in an HTTP request, use **$filename**. This will resolve to the file or folder that was matched and subsequently downloaded.

View File

@ -16,7 +16,9 @@ public interface ScheduleService {
Schedule fetchSchedule(Long id);
Schedule saveSchedule(Schedule schedule);
Schedule createSchedule(Schedule schedule);
Schedule updateSchedule(Schedule schedule);
void clearScannedFilesFromSchedule(Long id);
}

View File

@ -81,22 +81,47 @@ public class ScheduleServiceImpl implements ScheduleService {
}
@Override
public Schedule saveSchedule(Schedule schedule) {
public Schedule createSchedule(Schedule schedule) {
HostModel hostModel = hostDAO.fetchHost(schedule.getHost());
ScheduleModel model = scheduleConverter.convertFrom(schedule);
model.host = getHostForSchedule(schedule.getHost());
return scheduleConverter.convertTo(scheduleDAO.updateConfig(model));
}
@Override
public Schedule updateSchedule(Schedule schedule) {
if (null == schedule.getId())
throw new IllegalArgumentException("Schdule has no ID");
ScheduleModel existingModel = scheduleDAO.fetchSchedule(schedule.getId());
ScheduleModel model = scheduleConverter.convertFrom(schedule);
model.host = getHostForSchedule(schedule.getHost());
model.setLastRunTime(existingModel.getLastRunTime());
return scheduleConverter.convertTo(scheduleDAO.updateConfig(model));
}
@Override
public void clearScannedFilesFromSchedule(Long id) {
ScheduleModel model = scheduleDAO.fetchSchedule(id);
model.scannedFiles.clear();
scheduleDAO.updateConfig(model);
}
private HostModel getHostForSchedule(Long id) {
HostModel hostModel = hostDAO.fetchHost(id);
if (null == hostModel) {
LOGGER.info("Schedule is referencing a host that does not exist");
throw new IllegalArgumentException("Host with id " + schedule.getHost() + " does not exist.");
throw new IllegalArgumentException("Host with id " + id + " does not exist.");
}
ScheduleModel model = scheduleConverter.convertFrom(schedule);
model.host = hostModel;
ScheduleModel updatedModel = scheduleDAO.updateConfig(model);
return scheduleConverter.convertTo(updatedModel);
return hostModel;
}
private Schedule toSchedule(ScheduleModel model) {
@ -104,7 +129,7 @@ public class ScheduleServiceImpl implements ScheduleService {
Schedule convertTo = scheduleConverter.convertTo(model);
if (scheduleExecutor.isScheduleRunning(convertTo.getId())) {
convertTo.setRunning(true);
List<FTPTransfer> transfers = scheduleExecutor.getRunningSchedule(convertTo.getId()).getSchedule().getTransfers();
@ -113,32 +138,24 @@ public class ScheduleServiceImpl implements ScheduleService {
return convertTo;
}
private Transfer toTransfer(FTPTransfer ftpTransfer) {
Transfer transfer = new Transfer();
transfer.setFileName(ftpTransfer.getFile().getName());
transfer.setFileSize(ftpTransfer.getFile().getSize());
transfer.setDirectory(ftpTransfer.getFile().isDirectory());
transfer.setStatus(ftpTransfer.getState().toString());
if (null != ftpTransfer.getListener()) {
Progress progress = new Progress();
progress.setPercentageComplete(ftpTransfer.getListener().getProgress());
progress.setTransferSpeed(ftpTransfer.getListener().getTransferSpeed());
transfer.setProgress(progress);
}
return transfer;
}
@Override
public void clearScannedFilesFromSchedule(Long id) {
ScheduleModel model = scheduleDAO.fetchSchedule(id);
model.scannedFiles.clear();
scheduleDAO.updateConfig(model);
}
}

View File

@ -37,9 +37,9 @@ public class HttpAPICallAction implements PostDownloadAction {
headers.add("Content-Type", contentType);
LOGGER.info("Sending message to generic API for {}", execution.fileName);
HttpEntity<String> httpEntity = new HttpEntity<String>(body.replaceAll("\\$filename", execution.fileName), headers);
HttpEntity<String> httpEntity = new HttpEntity<String>(resolveFilename(body, execution.fileName), headers);
LOGGER.debug("Sending {} message {} to generic API: {}", method, httpEntity, url);
restTemplate.exchange(url, method, httpEntity, Object.class);
restTemplate.exchange(resolveFilename(url, execution.fileName), method, httpEntity, Object.class);
} catch (RestClientException | HttpMessageConversionException e) {
@ -52,4 +52,8 @@ public class HttpAPICallAction implements PostDownloadAction {
public String toString() {
return getClass().getSimpleName();
}
private String resolveFilename(String value, String filename) {
return value.replace("$filename", filename);
}
}

View File

@ -56,7 +56,7 @@ public class APIController {
try {
Schedule createdSchedule = scheduleService.saveSchedule(schedule);
Schedule createdSchedule = scheduleService.createSchedule(schedule);
LOGGER.info("New schedule has been created");
return ResponseEntity.status(HttpStatus.CREATED)
@ -99,7 +99,7 @@ public class APIController {
LOGGER.debug("Imposing id from URL into body");
schedule.setId(id);
Schedule updatedSchedule = scheduleService.saveSchedule(schedule);
Schedule updatedSchedule = scheduleService.updateSchedule(schedule);
LOGGER.debug("Schedule has been updated");
return ResponseEntity.status(HttpStatus.OK).body(APIResponseBuilder.create().withBody(updatedSchedule));

View File

@ -1,8 +1,19 @@
package io.linuxserver.davos.delegation.services;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.util.ArrayList;
import java.util.List;
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.mockito.Spy;
@ -10,23 +21,257 @@ import org.mockito.Spy;
import io.linuxserver.davos.converters.ScheduleConverter;
import io.linuxserver.davos.persistence.dao.HostDAO;
import io.linuxserver.davos.persistence.dao.ScheduleDAO;
import io.linuxserver.davos.persistence.model.HostModel;
import io.linuxserver.davos.persistence.model.ScannedFileModel;
import io.linuxserver.davos.persistence.model.ScheduleModel;
import io.linuxserver.davos.schedule.ScheduleExecutor;
import io.linuxserver.davos.web.Schedule;
public class ScheduleServiceImplTest {
@InjectMocks
private ScheduleService scheduleService = new ScheduleServiceImpl();
@Mock
private ScheduleDAO mockScheduleDAO;
@Spy
private ScheduleConverter mockScheduleConverter;
private ScheduleConverter scheduleConverter;
@Mock
private ScheduleDAO scheduleDAO;
private ScheduleExecutor mockExecutor;
@Mock
private HostDAO mockHostDAO;
@Captor
public ArgumentCaptor<ScheduleModel> scheduleCaptor;
@Before
public void before() {
initMocks(this);
}
@Test
public void shouldStartScheduleFromExecutor() {
scheduleService.startSchedule(1L);
verify(mockExecutor).startSchedule(1L);
}
@Test
public void shouldStopScheduleFromExecutor() {
scheduleService.stopSchedule(1L);
verify(mockExecutor).stopSchedule(1L);
}
@Test
public void shouldDeleteScheduleWhenNotRunning() {
scheduleService.deleteSchedule(1L);
verify(mockExecutor, never()).stopSchedule(1L);
verify(mockScheduleDAO).deleteSchedule(1L);
}
@Test
public void shouldCheckIfScheduleIsRunningAndStopIfSoBeforeDeleting() {
when(mockExecutor.isScheduleRunning(1L)).thenReturn(true);
scheduleService.deleteSchedule(1L);
verify(mockExecutor).stopSchedule(1L);
verify(mockScheduleDAO).deleteSchedule(1L);
}
@Test
public void shouldGetAllSchedulesAndConvert() {
List<ScheduleModel> models = new ArrayList<ScheduleModel>();
ScheduleModel model1 = new ScheduleModel();
model1.id = 1L;
model1.name = "Test 1";
model1.host = new HostModel();
ScheduleModel model2 = new ScheduleModel();
model2.id = 2L;
model2.name = "Test 2";
model2.host = new HostModel();
models.add(model1);
models.add(model2);
when(mockScheduleDAO.getAll()).thenReturn(models);
List<Schedule> schedules = scheduleService.fetchAllSchedules();
assertThat(schedules).hasSize(2);
assertThat(schedules.get(0).getId()).isEqualTo(1L);
assertThat(schedules.get(0).getName()).isEqualTo("Test 1");
assertThat(schedules.get(1).getId()).isEqualTo(2L);
assertThat(schedules.get(1).getName()).isEqualTo("Test 2");
}
@Test
public void shouldReturnOneSchedule() {
ScheduleModel model1 = new ScheduleModel();
model1.id = 1L;
model1.name = "Test 1";
model1.host = new HostModel();
when(mockScheduleDAO.fetchSchedule(1L)).thenReturn(model1);
Schedule schedule = scheduleService.fetchSchedule(1L);
assertThat(schedule.getId()).isEqualTo(1L);
assertThat(schedule.getName()).isEqualTo("Test 1");
}
@Test(expected = IllegalArgumentException.class)
public void shouldGetHostFromDatabaseToCheckItExistsWhenCreating() {
Schedule schedule = new Schedule();
schedule.setHost(null);
scheduleService.createSchedule(schedule);
}
@Test
public void shouldOverlayHostFromDatabaseInScheduleWhenCreating() {
ScheduleModel model1 = new ScheduleModel();
model1.id = 1L;
model1.name = "Test 1";
model1.host = new HostModel();
when(mockScheduleDAO.updateConfig(any(ScheduleModel.class))).thenReturn(model1);
HostModel hostModell = new HostModel();
when(mockHostDAO.fetchHost(2L)).thenReturn(hostModell);
Schedule schedule = new Schedule();
schedule.setHost(2L);
scheduleService.createSchedule(schedule);
verify(mockScheduleDAO).updateConfig(scheduleCaptor.capture());
assertThat(scheduleCaptor.getValue().host).isEqualTo(hostModell);
}
@Test
public void shouldReturnConvertedScheduleOnceCreated() {
ScheduleModel model1 = new ScheduleModel();
model1.id = 1L;
model1.name = "Test 1";
model1.host = new HostModel();
when(mockScheduleDAO.updateConfig(any(ScheduleModel.class))).thenReturn(model1);
HostModel hostModell = new HostModel();
when(mockHostDAO.fetchHost(2L)).thenReturn(hostModell);
Schedule schedule = new Schedule();
schedule.setHost(2L);
Schedule createdSchedule = scheduleService.createSchedule(schedule);
assertThat(createdSchedule.getId()).isEqualTo(1L);
assertThat(createdSchedule.getName()).isEqualTo("Test 1");
}
@Test
public void shouldOverlayHostFromDatabaseInScheduleWhenUpdating() {
setUpScheduleMocks();
HostModel hostModel1 = new HostModel();
when(mockHostDAO.fetchHost(2L)).thenReturn(hostModel1);
Schedule schedule = new Schedule();
schedule.setId(1L);
schedule.setHost(2L);
scheduleService.updateSchedule(schedule);
verify(mockScheduleDAO).updateConfig(scheduleCaptor.capture());
assertThat(scheduleCaptor.getValue().host).isEqualTo(hostModel1);
}
@Test
public void shouldReturnConvertedScheduleOnceUpdated() {
setUpScheduleMocks();
Schedule schedule = new Schedule();
schedule.setId(1L);
schedule.setHost(2L);
Schedule createdSchedule = scheduleService.updateSchedule(schedule);
assertThat(createdSchedule.getId()).isEqualTo(1L);
assertThat(createdSchedule.getName()).isEqualTo("Test 1");
}
@Test
public void shouldOverlayLastRunTimeOfExistingScheduleToNewOne() {
setUpScheduleMocks();
Schedule schedule = new Schedule();
schedule.setId(1L);
schedule.setHost(2L);
scheduleService.updateSchedule(schedule);
verify(mockScheduleDAO).updateConfig(scheduleCaptor.capture());
assertThat(scheduleCaptor.getValue().getLastRunTime()).isEqualTo(12345L);
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionIfScheduleHasNoIdWhenUpdating() {
Schedule schedule = new Schedule();
schedule.setHost(2L);
HostModel hostModell = new HostModel();
when(mockHostDAO.fetchHost(2L)).thenReturn(hostModell);
scheduleService.updateSchedule(schedule);
}
@Test
public void shouldClearScannedFiles() {
ScheduleModel model = new ScheduleModel();
model.scannedFiles = new ArrayList<ScannedFileModel>();
model.scannedFiles.add(new ScannedFileModel());
when(mockScheduleDAO.fetchSchedule(1L)).thenReturn(model);
assertThat(model.scannedFiles).hasSize(1);
scheduleService.clearScannedFilesFromSchedule(1L);
assertThat(model.scannedFiles).isEmpty();
verify(mockScheduleDAO).updateConfig(model);
}
private void setUpScheduleMocks() {
ScheduleModel model1 = new ScheduleModel();
model1.id = 1L;
model1.name = "Test 1";
model1.setLastRunTime(12345L);
model1.host = new HostModel();
when(mockScheduleDAO.updateConfig(any(ScheduleModel.class))).thenReturn(model1);
when(mockScheduleDAO.fetchSchedule(1L)).thenReturn(model1);
when(mockHostDAO.fetchHost(2L)).thenReturn(new HostModel());
}
}

View File

@ -54,6 +54,24 @@ public class HttpAPICallActionTest {
assertThat(body).isEqualTo("{\"hello\":\"file.txt\"}");
}
@Test
public void shouldResolveFilenameInUrlAsWell() {
PostDownloadExecution execution = new PostDownloadExecution();
execution.fileName = "file.txt";
httpAPICallAction = new HttpAPICallAction("http://url?file=$filename", "POST", "application/json", "{\"hello\":\"$filename\"}");
initMocks(this);
httpAPICallAction.execute(execution);
verify(mockRestTemplate).exchange(eq("http://url?file=file.txt"), eq(HttpMethod.POST), entityCaptor.capture(), eq(Object.class));
String body = entityCaptor.getValue().getBody();
assertThat(body).isEqualTo("{\"hello\":\"file.txt\"}");
}
@Test
public void postDataShouldHaveCorrectHeaderValue() {

View File

@ -41,7 +41,7 @@ public class APIControllerTest {
controller.createSchedule(schedule);
verify(mockScheduleFacade).saveSchedule(schedule);
verify(mockScheduleFacade).createSchedule(schedule);
}
@Test
@ -50,7 +50,7 @@ public class APIControllerTest {
Schedule newSchedule = new Schedule();
Schedule schedule = new Schedule();
when(mockScheduleFacade.saveSchedule(schedule)).thenReturn(newSchedule);
when(mockScheduleFacade.createSchedule(schedule)).thenReturn(newSchedule);
ResponseEntity<APIResponse> response = controller.createSchedule(schedule);
@ -65,7 +65,7 @@ public class APIControllerTest {
controller.updateSchedule(1L, schedule);
verify(mockScheduleFacade).saveSchedule(schedule);
verify(mockScheduleFacade).updateSchedule(schedule);
assertThat(schedule.getId()).isEqualTo(1L);
}
@ -75,7 +75,7 @@ public class APIControllerTest {
Schedule schedule = new Schedule();
when(mockScheduleFacade.saveSchedule(schedule)).thenReturn(schedule);
when(mockScheduleFacade.updateSchedule(schedule)).thenReturn(schedule);
ResponseEntity<APIResponse> response = controller.updateSchedule(1L, schedule);

View File

@ -1 +1 @@
2.2.0
2.2.2