Skip to content

Commit

Permalink
Add scanner
Browse files Browse the repository at this point in the history
  • Loading branch information
sven1103 committed Apr 4, 2024
1 parent d5e81fb commit 253bcdc
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 55 deletions.
32 changes: 0 additions & 32 deletions src/main/groovy/life/qbic/springminimaltemplate/AppConfig.groovy

This file was deleted.

This file was deleted.

41 changes: 41 additions & 0 deletions src/main/java/life/qbic/data/processing/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package life.qbic.data.processing;

import life.qbic.springminimaltemplate.CodingPrayersMessageService;
import life.qbic.springminimaltemplate.DeveloperNews;
import life.qbic.springminimaltemplate.MessageService;
import life.qbic.springminimaltemplate.NewsMedia;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
* <b>Spring configuration class</b>
*
* <p>Reads properties from a properties file and creates beans for the application.</p>
*
* @since 0.1.0
*/
@Configuration
@PropertySource("application.properties")
class AppConfig {

@Value("${messages.file}")
public String messagesFile;

@Bean
MessageService messageService() {
return new CodingPrayersMessageService(messagesFile);
}

@Bean
ScannerConfiguration scannerConfiguration(@Value("${scanner.directory}") String scannerDirectory) {
return new ScannerConfiguration(scannerDirectory);
}

@Bean
NewsMedia newsMedia() {
return new DeveloperNews(messageService());
}

}
27 changes: 27 additions & 0 deletions src/main/java/life/qbic/data/processing/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package life.qbic.data.processing;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
AppConfig.class);

ScannerConfiguration scannerConfiguration = context.getBean(ScannerConfiguration.class);
var scannerThread = new Scanner(scannerConfiguration, new ConcurrentEventQueue());
scannerThread.start();

Runtime.getRuntime().addShutdownHook(new Thread(null, scannerThread::interrupt, "Shutdown-thread"));

context.close();
}



}
50 changes: 50 additions & 0 deletions src/main/java/life/qbic/data/processing/ConcurrentEventQueue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package life.qbic.data.processing;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
* <b><class short description - 1 Line!></b>
*
* <p><More detailed description - When to use, what it solves, etc.></p>
*
* @since <version tag>
*/
public class ConcurrentEventQueue {

private final Queue<RegistrationRequestEvent> queue = new ConcurrentLinkedQueue<>();

private static final int DEFAULT_CAPACITY = 10;
private final int capacity;

public ConcurrentEventQueue() {
this(DEFAULT_CAPACITY);
}

public ConcurrentEventQueue(int capacity) {
this.capacity = capacity;
}

public synchronized void add(RegistrationRequestEvent event) {
while (queue.size() >= capacity) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
queue.add(event);
notify();
}

public synchronized RegistrationRequestEvent poll() {
while (queue.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return queue.poll();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package life.qbic.data.processing;

import java.nio.file.Path;
import java.time.Instant;

/**
* <b><record short description - 1 Line!></b>
*
* <p><More detailed description - When to use, what it solves, etc.></p>
*
* @since <version tag>
*/
public record RegistrationRequestEvent(Instant timestamp, Path origin, Path target) {

}
94 changes: 94 additions & 0 deletions src/main/java/life/qbic/data/processing/Scanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package life.qbic.data.processing;

import static org.apache.logging.log4j.LogManager.getLogger;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.apache.logging.log4j.Logger;

/**
* <b><class short description - 1 Line!></b>
*
* <p><More detailed description - When to use, what it solves, etc.></p>
*
* @since <version tag>
*/
public class Scanner extends Thread {

private static final Logger log = getLogger(Scanner.class);
private static final Path REGISTRATION_PATH = Paths.get("registration");
private final ScannerConfiguration configuration;
private final HashSet<Path> userProcessDirectories = new HashSet<>();
private final ConcurrentEventQueue eventQueue;

public Scanner(ScannerConfiguration scannerConfiguration, ConcurrentEventQueue eventQueue) {
this.setName("Scanner-Thread");
this.configuration = Objects.requireNonNull(scannerConfiguration,
"scannerConfiguration must not be null");
this.eventQueue = Objects.requireNonNull(eventQueue, "eventQueue must not be null");
}

@Override
public void run() {
log.info("Started scanning '%s'".formatted(configuration.scannerDirectory()));
var scannerPath = Paths.get(configuration.scannerDirectory());
while (!Thread.interrupted()) {
try {
var userFolderIterator = Arrays.stream(
Objects.requireNonNull(scannerPath.toFile().listFiles())).filter(File::isDirectory)
.toList().iterator();

while (userFolderIterator.hasNext()) {
fetchRegistrationDirectory(userFolderIterator.next().toPath()).ifPresent(
this::addRegistrationDirectory);
}

removePathZombies();

Thread.sleep(100);
} catch (InterruptedException e) {
interrupt();
}
}
}

private void removePathZombies() {
List<Path> zombies = new LinkedList<>();
for (Path processFolder : userProcessDirectories) {
if (!processFolder.toFile().exists()) {
zombies.add(processFolder);
}
}

zombies.forEach(zombie -> {
userProcessDirectories.remove(zombie);
log.warn("Removing orphaned process directory: '%s'".formatted(zombie));
});

}

private void addRegistrationDirectory(Path path) {
if (userProcessDirectories.add(path)) {
log.info("New user process directory found: '%s'".formatted(path.toString()));
}
}

public Optional<Path> fetchRegistrationDirectory(Path userDirectory) {
Path resolvedPath = userDirectory.resolve(REGISTRATION_PATH);
return Optional.ofNullable(resolvedPath.toFile().exists() ? resolvedPath : null);
}

@Override
public void interrupt() {
log.warn("Received interrupt signal, cleaning up and shutting down.");
}


}
23 changes: 23 additions & 0 deletions src/main/java/life/qbic/data/processing/ScannerConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package life.qbic.data.processing;

import org.springframework.stereotype.Component;

/**
* <b><class short description - 1 Line!></b>
*
* <p><More detailed description - When to use, what it solves, etc.></p>
*
* @since <version tag>
*/
public class ScannerConfiguration {

private final String scannerDirectory;

public ScannerConfiguration(String scannerDirectory) {
this.scannerDirectory = scannerDirectory;
}

public String scannerDirectory() {
return scannerDirectory;
}
}
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
messages.file=messages.txt
scanner.directory=/Users/sven1103/Downloads/scanner-test

0 comments on commit 253bcdc

Please sign in to comment.